import {
  Layout
} from "./chunk-VE4LENUR.js";
import {
  ErrorCause
} from "./chunk-PINJDICN.js";
import {
  ErrorInfo
} from "./chunk-3X66E37A.js";
import {
  ErrorMetadata
} from "./chunk-UKBVLD72.js";
import {
  ErrorStack
} from "./chunk-JAN2TFI2.js";
import {
  ErrorStackSource
} from "./chunk-4XB2BYKC.js";
import "./chunk-4L7RY2JA.js";
import {
  Header
} from "./chunk-PUHGL6HA.js";
import "./chunk-OSUFJZHZ.js";
import {
  BaseComponent
} from "./chunk-4YEN7HVQ.js";
// src/youch.ts
import cookie from "cookie";
import { ErrorParser } from "youch-core";
// src/metadata.ts
var Metadata = class {
  #groups = {};
  /**
   * Converts value to an array (if not an array already)
   */
  #toArray(value) {
    return Array.isArray(value) ? value : [value];
  }
  /**
   * Define a group, its sections and their rows. In case of
   * existing groups/sections, the new data will be merged
   * with the existing data
   */
  group(name, sections) {
    this.#groups[name] = this.#groups[name] ?? {};
    Object.keys(sections).forEach((section) => {
      if (!this.#groups[name][section]) {
        this.#groups[name][section] = sections[section];
      } else {
        this.#groups[name][section] = this.#toArray(this.#groups[name][section]);
        this.#groups[name][section].push(...this.#toArray(sections[section]));
      }
    });
    return this;
  }
  /**
   * Returns the existing metadata groups, sections and
   * rows.
   */
  toJSON() {
    return this.#groups;
  }
};
// src/templates.ts
import { createScript, createStyleSheet } from "@poppinss/dumper/html";
var Templates = class {
  constructor(devMode) {
    this.devMode = devMode;
    this.#knownTemplates = {
      layout: new Layout(devMode),
      header: new Header(devMode),
      errorInfo: new ErrorInfo(devMode),
      errorStack: new ErrorStack(devMode),
      errorStackSource: new ErrorStackSource(devMode),
      errorCause: new ErrorCause(devMode),
      errorMetadata: new ErrorMetadata(devMode)
    };
  }
  #knownTemplates;
  #styles = /* @__PURE__ */ new Map([["global", createStyleSheet()]]);
  #scripts = /* @__PURE__ */ new Map([["global", createScript()]]);
  /**
   * Returns a collection of style and script tags to dump
   * inside the document HEAD.
   */
  #getStylesAndScripts(cspNonce) {
    let customInjectedStyles = "";
    const styles = [];
    const scripts = [];
    const cspNonceAttr = cspNonce ? ` nonce="${cspNonce}"` : "";
    this.#styles.forEach((bucket, name) => {
      if (name === "injected") {
        customInjectedStyles = ``;
      } else {
        styles.push(``);
      }
    });
    this.#scripts.forEach((bucket, name) => {
      scripts.push(``);
    });
    return { styles: `${styles.join("\n")}
${customInjectedStyles}`, scripts: scripts.join("\n") };
  }
  /**
   * Collects styles and scripts for components as we render
   * them.
   */
  async #collectStylesAndScripts(templateName) {
    if (!this.#styles.has(templateName)) {
      const styles = await this.#knownTemplates[templateName].getStyles();
      if (styles) {
        this.#styles.set(templateName, styles);
      }
    }
    if (!this.#scripts.has(templateName)) {
      const script = await this.#knownTemplates[templateName].getScript();
      if (script) {
        this.#scripts.set(templateName, script);
      }
    }
  }
  /**
   * Returns the HTML for a given template
   */
  async #tmplToHTML(templateName, props) {
    const component = this.#knownTemplates[templateName];
    if (!component) {
      throw new Error(`Invalid template "${templateName}"`);
    }
    await this.#collectStylesAndScripts(templateName);
    return component.toHTML(props);
  }
  /**
   * Returns the ANSI output for a given template
   */
  async #tmplToANSI(templateName, props) {
    const component = this.#knownTemplates[templateName];
    if (!component) {
      throw new Error(`Invalid template "${templateName}"`);
    }
    return component.toANSI(props);
  }
  /**
   * Define a custom component to be used in place of the default component.
   * Overriding components allows you control the HTML layout, styles and
   * the frontend scripts of an HTML fragment.
   */
  use(templateName, component) {
    this.#knownTemplates[templateName] = component;
    return this;
  }
  /**
   * Inject custom styles to the document. Injected styles are
   * always placed after the global and the components style
   * tags.
   */
  injectStyles(cssFragment) {
    let injectedStyles = this.#styles.get("injected") ?? "";
    injectedStyles += `
${cssFragment}`;
    this.#styles.set("injected", injectedStyles);
    return this;
  }
  /**
   * Returns the HTML output for the given parsed error
   */
  async toHTML(props) {
    const html = await this.#tmplToHTML("layout", {
      title: props.title,
      ide: props.ide,
      cspNonce: props.cspNonce,
      children: async () => {
        const header = await this.#tmplToHTML("header", props);
        const info = await this.#tmplToHTML("errorInfo", props);
        const stackTrace = await this.#tmplToHTML("errorStack", {
          ide: process.env.EDITOR ?? "vscode",
          sourceCodeRenderer: (error, frame) => {
            return this.#tmplToHTML("errorStackSource", {
              error,
              frame,
              ide: props.ide,
              cspNonce: props.cspNonce
            });
          },
          ...props
        });
        const cause = await this.#tmplToHTML("errorCause", props);
        const metadata = await this.#tmplToHTML("errorMetadata", props);
        return `${header}${info}${stackTrace}${cause}${metadata}`;
      }
    });
    const { scripts, styles } = this.#getStylesAndScripts(props.cspNonce);
    return html.replace("", styles).replace("", scripts);
  }
  /**
   * Returns the ANSI output to be printed on the terminal
   */
  async toANSI(props) {
    const ansiOutput = await this.#tmplToANSI("layout", {
      title: props.title,
      children: async () => {
        const header = await this.#tmplToANSI("header", {});
        const info = await this.#tmplToANSI("errorInfo", props);
        const stackTrace = await this.#tmplToANSI("errorStack", {
          ide: process.env.EDITOR ?? "vscode",
          sourceCodeRenderer: (error, frame) => {
            return this.#tmplToANSI("errorStackSource", {
              error,
              frame
            });
          },
          ...props
        });
        const cause = await this.#tmplToANSI("errorCause", props);
        const metadata = await this.#tmplToANSI("errorMetadata", props);
        return `${header}${info}${stackTrace}${cause}${metadata}`;
      }
    });
    return ansiOutput;
  }
};
// src/youch.ts
var Youch = class {
  /**
   * Properties to be shared with the Error parser
   */
  #sourceLoader;
  #parsers = [];
  #transformers = [];
  /**
   * Manage templates used for converting error to the HTML
   * output
   */
  templates = new Templates(false);
  /**
   * Define metadata to be displayed alongside the error output
   */
  metadata = new Metadata();
  /**
   * Creates an instance of the ErrorParser and applies the
   * source loader, parsers and transformers on it
   */
  #createErrorParser(options) {
    const errorParser = new ErrorParser(options);
    if (this.#sourceLoader) {
      errorParser.defineSourceLoader(this.#sourceLoader);
    }
    this.#parsers.forEach((parser) => errorParser.useParser(parser));
    this.#transformers.forEach((transformer) => errorParser.useTransformer(transformer));
    return errorParser;
  }
  /**
   * Defines the request properties as a metadata group
   */
  #defineRequestMetadataGroup(request) {
    if (!request || Object.keys(request).length === 0) {
      return;
    }
    this.metadata.group("Request", {
      ...request.url ? {
        url: {
          key: "URL",
          value: request.url
        }
      } : {},
      ...request.method ? {
        method: {
          key: "Method",
          value: request.method
        }
      } : {},
      ...request.headers ? {
        headers: Object.keys(request.headers).map((key) => {
          const value = request.headers[key];
          return {
            key,
            value: key === "cookie" ? { ...cookie.parse(value) } : value
          };
        })
      } : {}
    });
  }
  /**
   * Define custom implementation for loading the source code
   * of a stack frame.
   */
  defineSourceLoader(loader) {
    this.#sourceLoader = loader;
    return this;
  }
  /**
   * Define a custom parser. Parsers are executed before the
   * error gets parsed and provides you with an option to
   * modify the error
   */
  useParser(parser) {
    this.#parsers.push(parser);
    return this;
  }
  /**
   * Define a custom transformer. Transformers are executed
   * after the error has been parsed and can mutate the
   * properties of the parsed error.
   */
  useTransformer(transformer) {
    this.#transformers.push(transformer);
    return this;
  }
  /**
   * Parses error to JSON
   */
  async toJSON(error, options) {
    options = { ...options };
    return this.#createErrorParser({ offset: options.offset }).parse(error);
  }
  /**
   * Render error to HTML
   */
  async toHTML(error, options) {
    options = { ...options };
    this.#defineRequestMetadataGroup(options.request);
    const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error);
    return this.templates.toHTML({
      title: options.title ?? "An error has occurred",
      ide: options.ide ?? process.env.IDE ?? "vscode",
      cspNonce: options.cspNonce,
      error: parsedError,
      metadata: this.metadata
    });
  }
  /**
   * Render error to ANSI output
   */
  async toANSI(error, options) {
    options = { ...options };
    const parsedError = await this.#createErrorParser({ offset: options.offset }).parse(error);
    return this.templates.toANSI({
      title: "",
      error: parsedError,
      metadata: this.metadata
    });
  }
};
export {
  BaseComponent,
  Metadata,
  Youch
};