import * as Arr from "./Array.js";
import { format, formatPath } from "./Formatter.js";
import * as InternalAnnotations from "./internal/schema/annotations.js";
import * as Option from "./Option.js";
import { hasProperty } from "./Predicate.js";
import * as Redacted from "./Redacted.js";
const TypeId = "~effect/SchemaIssue/Issue";
/**
 * Returns `true` if the given value is an {@link Issue}.
 *
 * When to use:
 *
 * - Narrowing an `unknown` value to `Issue` in error-handling code.
 * - Distinguishing an `Issue` from other error types in a catch-all handler.
 *
 * Behaviour:
 *
 * - Pure; does not mutate input.
 * - Checks for the internal `TypeId` brand on the value.
 *
 * **Example** (Type-guarding an unknown error)
 *
 * ```ts
 * import { SchemaIssue } from "effect"
 *
 * const issue = new SchemaIssue.MissingKey(undefined)
 * console.log(SchemaIssue.isIssue(issue))
 * // true
 * console.log(SchemaIssue.isIssue("not an issue"))
 * // false
 * ```
 *
 * @see {@link Issue}
 *
 * @since 4.0.0
 */
export function isIssue(u) {
  return hasProperty(u, TypeId);
}
class Base {
  [TypeId] = TypeId;
  toString() {
    return defaultFormatter(this);
  }
}
/**
 * Issue produced when a schema filter (refinement check) fails.
 *
 * When to use:
 *
 * - Inspect which filter rejected the value.
 * - Walk the inner `issue` for the specific validation failure.
 *
 * Behaviour:
 *
 * - `actual` is the raw input value that was tested (plain `unknown`, not
 *   wrapped in `Option`).
 * - `filter` is the AST filter node that produced this issue.
 * - `issue` is the inner issue describing the failure reason.
 *
 * **Example** (Matching a Filter issue)
 *
 * ```ts
 * import { SchemaIssue } from "effect"
 *
 * function describe(issue: SchemaIssue.Issue): string {
 *   if (issue._tag === "Filter") {
 *     return `Filter failed on: ${JSON.stringify(issue.actual)}`
 *   }
 *   return String(issue)
 * }
 * ```
 *
 * @see {@link Leaf} — terminal issue types that commonly appear as the inner `issue`
 * @see {@link CheckHook} — formatter hook for `Filter` issues
 *
 * @category model
 * @since 4.0.0
 */
export class Filter extends Base {
  _tag = "Filter";
  /**
   * The input value that caused the issue.
   */
  actual;
  /**
   * The filter that failed.
   */
  filter;
  /**
   * The issue that occurred.
   */
  issue;
  constructor(
  /**
   * The input value that caused the issue.
   */
  actual,
  /**
   * The filter that failed.
   */
  filter,
  /**
   * The issue that occurred.
   */
  issue) {
    super();
    this.actual = actual;
    this.filter = filter;
    this.issue = issue;
  }
}
/**
 * Issue produced when a schema transformation (encode/decode step) fails.
 *
 * When to use:
 *
 * - Inspect failures from `Schema.decodeTo` / `Schema.encodeTo`
 *   transformations.
 * - Walk the inner `issue` for the root cause of the transformation failure.
 *
 * Behaviour:
 *
 * - `ast` is the AST node for the transformation that failed.
 * - `actual` is `Option.some(value)` when the input was present, or
 *   `Option.none()` when it was absent.
 * - `issue` is the inner issue describing the failure.
 *
 * @see {@link Filter} — failure from a refinement check (not a transformation)
 * @see {@link Composite} — multiple issues from a single schema node
 *
 * @category model
 * @since 4.0.0
 */
export class Encoding extends Base {
  _tag = "Encoding";
  /**
   * The schema that caused the issue.
   */
  ast;
  /**
   * The input value that caused the issue.
   */
  actual;
  /**
   * The issue that occurred.
   */
  issue;
  constructor(
  /**
   * The schema that caused the issue.
   */
  ast,
  /**
   * The input value that caused the issue.
   */
  actual,
  /**
   * The issue that occurred.
   */
  issue) {
    super();
    this.ast = ast;
    this.actual = actual;
    this.issue = issue;
  }
}
/**
 * Wraps an inner {@link Issue} with a property-key path, indicating *where* in
 * a nested structure the error occurred.
 *
 * When to use:
 *
 * - Walk the issue tree to accumulate path segments for error reporting.
 * - Match on `_tag === "Pointer"` when flattening nested issues.
 *
 * Behaviour:
 *
 * - `path` is an array of property keys (strings, numbers, or symbols).
 * - Has no `actual` value — {@link getActual} returns `Option.none()`.
 * - Formatters concatenate nested `Pointer` paths into a single path like
 *   `["a"]["b"][0]`.
 *
 * @see {@link getActual} — returns `Option.none()` for `Pointer`
 * @see {@link Composite} — groups multiple issues under one schema node
 *
 * @category model
 * @since 4.0.0
 */
export class Pointer extends Base {
  _tag = "Pointer";
  /**
   * The path to the location in the input that caused the issue.
   */
  path;
  /**
   * The issue that occurred.
   */
  issue;
  constructor(
  /**
   * The path to the location in the input that caused the issue.
   */
  path,
  /**
   * The issue that occurred.
   */
  issue) {
    super();
    this.path = path;
    this.issue = issue;
  }
}
/**
 * Issue produced when a required key or tuple index is missing from the input.
 *
 * When to use:
 *
 * - Detect absent fields in struct/tuple validation.
 * - Typically found inside a {@link Pointer} that indicates which key is
 *   missing.
 *
 * Behaviour:
 *
 * - Has no `actual` value — {@link getActual} returns `Option.none()`.
 * - `annotations` may contain a custom `messageMissingKey` for formatting.
 *
 * @see {@link Pointer} — wraps this issue with the missing key's path
 * @see {@link UnexpectedKey} — the opposite case (extra key present)
 *
 * @category model
 * @since 4.0.0
 */
export class MissingKey extends Base {
  _tag = "MissingKey";
  /**
   * The metadata for the issue.
   */
  annotations;
  constructor(
  /**
   * The metadata for the issue.
   */
  annotations) {
    super();
    this.annotations = annotations;
  }
}
/**
 * Issue produced when an input object or tuple contains a key/index not
 * declared by the schema.
 *
 * When to use:
 *
 * - Detect excess properties during strict struct/tuple validation.
 * - Typically found inside a {@link Pointer} that indicates which key was
 *   unexpected.
 *
 * Behaviour:
 *
 * - `actual` is the raw value at the unexpected key (plain `unknown`).
 * - `ast` is the schema that was being validated against.
 * - `annotations` on `ast` may contain a custom `messageUnexpectedKey`.
 *
 * @see {@link MissingKey} — the opposite case (required key absent)
 * @see {@link Pointer} — wraps this issue with the unexpected key's path
 *
 * @category model
 * @since 4.0.0
 */
export class UnexpectedKey extends Base {
  _tag = "UnexpectedKey";
  /**
   * The schema that caused the issue.
   */
  ast;
  /**
   * The input value that caused the issue.
   */
  actual;
  constructor(
  /**
   * The schema that caused the issue.
   */
  ast,
  /**
   * The input value that caused the issue.
   */
  actual) {
    super();
    this.ast = ast;
    this.actual = actual;
  }
}
/**
 * Issue that groups multiple child issues under a single schema node.
 *
 * When to use:
 *
 * - Walk the issue tree for struct/tuple schemas that collect all field errors
 *   rather than failing on the first.
 * - Match on `_tag === "Composite"` to iterate over `issues`.
 *
 * Behaviour:
 *
 * - `issues` is a non-empty readonly array (at least one child).
 * - `actual` is `Option.some(value)` when the input was present, or
 *   `Option.none()` when absent.
 * - Formatters flatten `Composite` by recursing into each child.
 *
 * @see {@link AnyOf} — used for union no-match errors (similar but different semantics)
 * @see {@link Pointer} — adds path context to individual issues
 *
 * @category model
 * @since 4.0.0
 */
export class Composite extends Base {
  _tag = "Composite";
  /**
   * The schema that caused the issue.
   */
  ast;
  /**
   * The input value that caused the issue.
   */
  actual;
  /**
   * The issues that occurred.
   */
  issues;
  constructor(
  /**
   * The schema that caused the issue.
   */
  ast,
  /**
   * The input value that caused the issue.
   */
  actual,
  /**
   * The issues that occurred.
   */
  issues) {
    super();
    this.ast = ast;
    this.actual = actual;
    this.issues = issues;
  }
}
/**
 * Issue produced when the runtime type of the input does not match the type
 * expected by the schema (e.g. got `null` when `string` was expected).
 *
 * When to use:
 *
 * - Detect basic type mismatches (wrong primitive, null where object expected,
 *   etc.).
 * - The most common leaf issue in typical validation failures.
 *
 * Behaviour:
 *
 * - `ast` is the schema node that expected a different type.
 * - `actual` is `Option.some(value)` when the input was present, or
 *   `Option.none()` when no value was provided.
 * - The default formatter renders this as `"Expected <type>, got <actual>"`.
 *
 * **Example** (Formatted output)
 *
 * ```ts
 * import { Schema } from "effect"
 *
 * try {
 *   Schema.decodeUnknownSync(Schema.String)(42)
 * } catch (e) {
 *   if (Schema.isSchemaError(e)) {
 *     console.log(String(e.issue))
 *     // "Expected string, got 42"
 *   }
 * }
 * ```
 *
 * @see {@link InvalidValue} — the input has the right type but fails a value constraint
 *
 * @category model
 * @since 4.0.0
 */
export class InvalidType extends Base {
  _tag = "InvalidType";
  /**
   * The schema that caused the issue.
   */
  ast;
  /**
   * The input value that caused the issue.
   */
  actual;
  constructor(
  /**
   * The schema that caused the issue.
   */
  ast,
  /**
   * The input value that caused the issue.
   */
  actual) {
    super();
    this.ast = ast;
    this.actual = actual;
  }
}
/**
 * Issue produced when the input has the correct type but its value violates a
 * constraint (e.g. a string that is too short, a number out of range).
 *
 * When to use:
 *
 * - Detect constraint violations from `Schema.filter`, `Schema.minLength`,
 *   `Schema.greaterThan`, etc.
 * - Create custom validation errors in `Schema.makeFilter` callbacks.
 *
 * Behaviour:
 *
 * - `actual` is `Option.some(value)` when the failing value is known, or
 *   `Option.none()` when absent.
 * - `annotations` optionally carries a `message` string for formatting.
 * - The default formatter renders this as `"Invalid data <actual>"` unless a
 *   custom `message` annotation is provided.
 *
 * **Example** (Custom filter returning InvalidValue)
 *
 * ```ts
 * import { Option, SchemaIssue } from "effect"
 *
 * const issue = new SchemaIssue.InvalidValue(
 *   Option.some(""),
 *   { message: "must not be empty" }
 * )
 * console.log(String(issue))
 * // "must not be empty"
 * ```
 *
 * @see {@link InvalidType} — the input has the wrong type entirely
 * @see {@link Filter} — composite wrapper when a schema filter produces this issue
 *
 * @category model
 * @since 4.0.0
 */
export class InvalidValue extends Base {
  _tag = "InvalidValue";
  /**
   * The value that caused the issue.
   */
  actual;
  /**
   * The metadata for the issue.
   */
  annotations;
  constructor(
  /**
   * The value that caused the issue.
   */
  actual,
  /**
   * The metadata for the issue.
   */
  annotations) {
    super();
    this.actual = actual;
    this.annotations = annotations;
  }
}
/**
 * Issue produced when a forbidden operation is encountered during parsing,
 * such as an asynchronous Effect running inside `Schema.decodeUnknownSync`.
 *
 * When to use:
 *
 * - Detect that a schema requires async execution but was run synchronously.
 * - Provide custom error messages via the `annotations.message` field.
 *
 * Behaviour:
 *
 * - `actual` is `Option.some(value)` when the input is known, or
 *   `Option.none()` when absent.
 * - `annotations` optionally carries a `message` string.
 * - The default formatter renders this as `"Forbidden operation"`.
 *
 * **Example** (Creating a Forbidden issue)
 *
 * ```ts
 * import { Option, SchemaIssue } from "effect"
 *
 * const issue = new SchemaIssue.Forbidden(
 *   Option.none(),
 *   { message: "async operation not allowed in sync context" }
 * )
 * console.log(String(issue))
 * // "async operation not allowed in sync context"
 * ```
 *
 * @see {@link InvalidValue} — for value-constraint failures (not operation failures)
 *
 * @category model
 * @since 4.0.0
 */
export class Forbidden extends Base {
  _tag = "Forbidden";
  /**
   * The input value that caused the issue.
   */
  actual;
  /**
   * The metadata for the issue.
   */
  annotations;
  constructor(
  /**
   * The input value that caused the issue.
   */
  actual,
  /**
   * The metadata for the issue.
   */
  annotations) {
    super();
    this.actual = actual;
    this.annotations = annotations;
  }
}
/**
 * Issue produced when a value does not match *any* member of a union schema.
 *
 * When to use:
 *
 * - Inspect which union members were attempted and why each failed.
 * - `issues` may be empty when the union has no members or when the input does
 *   not pass the initial type guard.
 *
 * Behaviour:
 *
 * - `ast` is the `Union` AST node.
 * - `actual` is the raw input value (plain `unknown`).
 * - `issues` contains per-member failures. When empty, the formatter falls
 *   back to the union's `expected` annotation.
 *
 * @see {@link OneOf} — the opposite: *too many* members matched
 * @see {@link Composite} — groups multiple issues under a non-union schema
 *
 * @category model
 * @since 4.0.0
 */
export class AnyOf extends Base {
  _tag = "AnyOf";
  /**
   * The schema that caused the issue.
   */
  ast;
  /**
   * The input value that caused the issue.
   */
  actual;
  /**
   * The issues that occurred.
   */
  issues;
  constructor(
  /**
   * The schema that caused the issue.
   */
  ast,
  /**
   * The input value that caused the issue.
   */
  actual,
  /**
   * The issues that occurred.
   */
  issues) {
    super();
    this.ast = ast;
    this.actual = actual;
    this.issues = issues;
  }
}
/**
 * Issue produced when a value matches *multiple* members of a union that is
 * configured to allow exactly one match (oneOf mode).
 *
 * When to use:
 *
 * - Detect ambiguous union matches when `oneOf` validation is enabled.
 * - Inspect `successes` to see which members matched.
 *
 * Behaviour:
 *
 * - `ast` is the `Union` AST node.
 * - `actual` is the raw input value (plain `unknown`).
 * - `successes` lists the AST nodes of each member that accepted the input.
 * - The default formatter renders this as
 *   `"Expected exactly one member to match the input <actual>"`.
 *
 * @see {@link AnyOf} — the opposite: *no* members matched
 *
 * @category model
 * @since 4.0.0
 */
export class OneOf extends Base {
  _tag = "OneOf";
  /**
   * The schema that caused the issue.
   */
  ast;
  /**
   * The input value that caused the issue.
   */
  actual;
  /**
   * The schemas that were successful.
   */
  successes;
  constructor(
  /**
   * The schema that caused the issue.
   */
  ast,
  /**
   * The input value that caused the issue.
   */
  actual,
  /**
   * The schemas that were successful.
   */
  successes) {
    super();
    this.ast = ast;
    this.actual = actual;
    this.successes = successes;
  }
}
/**
 * Extracts the actual input value from any {@link Issue} variant.
 *
 * When to use:
 *
 * - Retrieve the offending value for logging or custom error rendering.
 * - Uniformly access `actual` regardless of which issue variant you have.
 *
 * Behaviour:
 *
 * - Pure; does not mutate the issue.
 * - Returns `Option.none()` for `Pointer` and `MissingKey` (they carry no
 *   value).
 * - Returns the existing `Option` for variants that already store `actual` as
 *   `Option<unknown>` (`InvalidType`, `InvalidValue`, `Forbidden`, `Encoding`,
 *   `Composite`).
 * - Wraps `actual` with `Option.some` for variants that store it as plain
 *   `unknown` (`AnyOf`, `UnexpectedKey`, `OneOf`, `Filter`).
 *
 * **Example** (Extracting the actual value)
 *
 * ```ts
 * import { Option, SchemaIssue } from "effect"
 *
 * const issue = new SchemaIssue.MissingKey(undefined)
 * console.log(SchemaIssue.getActual(issue))
 * // { _tag: "None" }
 * ```
 *
 * @see {@link Issue}
 * @see {@link isIssue}
 *
 * @since 4.0.0
 */
export function getActual(issue) {
  switch (issue._tag) {
    case "Pointer":
    case "MissingKey":
      return Option.none();
    case "InvalidType":
    case "InvalidValue":
    case "Forbidden":
    case "Encoding":
    case "Composite":
      return issue.actual;
    case "AnyOf":
    case "UnexpectedKey":
    case "OneOf":
    case "Filter":
      return Option.some(issue.actual);
  }
}
function makeFilterIssue(input, entry) {
  if (isIssue(entry)) {
    return entry;
  }
  if (typeof entry === "string") {
    return new InvalidValue(Option.some(input), {
      message: entry
    });
  }
  const inner = typeof entry.issue === "string" ? new InvalidValue(Option.some(input), {
    message: entry.issue
  }) : entry.issue;
  return new Pointer(entry.path, inner);
}
/** @internal */
export function makeSingle(input, out) {
  if (out === undefined) {
    return undefined;
  }
  if (typeof out === "boolean") {
    return out ? undefined : new InvalidValue(Option.some(input));
  }
  return makeFilterIssue(input, out);
}
/** @internal */
export function make(input, ast, out) {
  if (Array.isArray(out)) {
    if (Arr.isReadonlyArrayNonEmpty(out)) {
      if (out.length === 1) {
        return makeFilterIssue(input, out[0]);
      }
      return new Composite(ast, Option.some(input), Arr.map(out, entry => makeFilterIssue(input, entry)));
    }
    return undefined;
  }
  return makeSingle(input, out);
}
/**
 * The built-in {@link LeafHook} used by default formatters.
 *
 * When to use:
 *
 * - Use as-is when you only need to customise the {@link CheckHook} but want
 *   the default leaf rendering.
 * - Reference as a starting point for custom `LeafHook` implementations.
 *
 * Behaviour:
 *
 * - Checks for a `message` annotation first; returns it if present.
 * - Otherwise generates a default message per `_tag`:
 *   - `InvalidType` → `"Expected <type>, got <actual>"`
 *   - `InvalidValue` → `"Invalid data <actual>"`
 *   - `MissingKey` → `"Missing key"`
 *   - `UnexpectedKey` → `"Unexpected key with value <actual>"`
 *   - `Forbidden` → `"Forbidden operation"`
 *   - `OneOf` → `"Expected exactly one member to match the input <actual>"`
 *
 * **Example** (Using defaultLeafHook with Standard Schema formatter)
 *
 * ```ts
 * import { SchemaIssue } from "effect"
 *
 * const formatter = SchemaIssue.makeFormatterStandardSchemaV1({
 *   leafHook: SchemaIssue.defaultLeafHook
 * })
 * ```
 *
 * @see {@link LeafHook}
 * @see {@link makeFormatterStandardSchemaV1}
 *
 * @category Formatter
 * @since 4.0.0
 */
export const defaultLeafHook = issue => {
  const message = findMessage(issue);
  if (message !== undefined) return message;
  switch (issue._tag) {
    case "InvalidType":
      return getExpectedMessage(InternalAnnotations.getExpected(issue.ast), formatOption(issue.actual));
    case "InvalidValue":
      return `Invalid data ${formatOption(issue.actual)}`;
    case "MissingKey":
      return "Missing key";
    case "UnexpectedKey":
      return `Unexpected key with value ${format(issue.actual)}`;
    case "Forbidden":
      return "Forbidden operation";
    case "OneOf":
      return `Expected exactly one member to match the input ${format(issue.actual)}`;
  }
};
/**
 * The built-in {@link CheckHook} used by default formatters.
 *
 * When to use:
 *
 * - Use as-is when you only need to customise the {@link LeafHook} but want
 *   the default filter rendering.
 *
 * Behaviour:
 *
 * - Looks for a `message` annotation on the inner issue first, then on the
 *   filter itself.
 * - Returns `undefined` when no annotation is found, causing the formatter to
 *   fall back to `"Expected <filter>, got <actual>"`.
 *
 * @see {@link CheckHook}
 * @see {@link makeFormatterStandardSchemaV1}
 *
 * @category Formatter
 * @since 4.0.0
 */
export const defaultCheckHook = issue => {
  return findMessage(issue.issue) ?? findMessage(issue);
};
/**
 * Creates a {@link Formatter} that produces a `StandardSchemaV1.FailureResult`.
 *
 * When to use:
 *
 * - Integrate with libraries that consume the
 *   [Standard Schema V1](https://github.com/standard-schema/standard-schema)
 *   error format.
 * - Customise error rendering by providing `leafHook` and/or `checkHook`.
 *
 * Behaviour:
 *
 * - Returns a `Formatter<StandardSchemaV1.FailureResult>`.
 * - Each leaf issue is flattened into `{ message, path }` entries.
 * - `Pointer` paths are accumulated to produce full property paths.
 * - Falls back to {@link defaultLeafHook} / {@link defaultCheckHook} when no
 *   hooks are provided.
 *
 * **Example** (Creating a Standard Schema V1 formatter)
 *
 * ```ts
 * import { SchemaIssue } from "effect"
 *
 * const formatter = SchemaIssue.makeFormatterStandardSchemaV1()
 * ```
 *
 * @see {@link makeFormatterDefault} — produces a plain string instead
 * @see {@link LeafHook}
 * @see {@link CheckHook}
 *
 * @category Formatter
 * @since 4.0.0
 */
export function makeFormatterStandardSchemaV1(options) {
  return issue => ({
    issues: toDefaultIssues(issue, [], options?.leafHook ?? defaultLeafHook, options?.checkHook ?? defaultCheckHook)
  });
}
function getExpectedMessage(expected, actual) {
  return `Expected ${expected}, got ${actual}`;
}
function toDefaultIssues(issue, path, leafHook, checkHook) {
  switch (issue._tag) {
    case "Filter":
      {
        const message = checkHook(issue);
        if (message !== undefined) {
          return [{
            path,
            message
          }];
        }
        switch (issue.issue._tag) {
          case "InvalidValue":
            return [{
              path,
              message: getExpectedMessage(formatCheck(issue.filter), format(issue.actual))
            }];
          default:
            return toDefaultIssues(issue.issue, path, leafHook, checkHook);
        }
      }
    case "Encoding":
      return toDefaultIssues(issue.issue, path, leafHook, checkHook);
    case "Pointer":
      return toDefaultIssues(issue.issue, [...path, ...issue.path], leafHook, checkHook);
    case "Composite":
      return issue.issues.flatMap(issue => toDefaultIssues(issue, path, leafHook, checkHook));
    case "AnyOf":
      {
        const message = findMessage(issue);
        if (issue.issues.length === 0) {
          if (message !== undefined) return [{
            path,
            message
          }];
          const expected = getExpectedMessage(InternalAnnotations.getExpected(issue.ast), format(issue.actual));
          return [{
            path,
            message: expected
          }];
        }
        return issue.issues.flatMap(issue => toDefaultIssues(issue, path, leafHook, checkHook));
      }
    default:
      return [{
        path,
        message: leafHook(issue)
      }];
  }
}
function formatCheck(check) {
  const expected = check.annotations?.expected;
  if (typeof expected === "string") return expected;
  switch (check._tag) {
    case "Filter":
      return "<filter>";
    case "FilterGroup":
      return check.checks.map(check => formatCheck(check)).join(" & ");
  }
}
/**
 * Creates a {@link Formatter} that converts an {@link Issue} into a
 * human-readable multi-line string.
 *
 * When to use:
 *
 * - Produce error messages for logging, CLI output, or developer-facing
 *   diagnostics.
 * - This is the default formatter used by `Issue.toString()`.
 *
 * Behaviour:
 *
 * - Flattens the issue tree into `{ message, path }` entries using
 *   {@link defaultLeafHook} and {@link defaultCheckHook}.
 * - Each entry is rendered as `"<message>"` or `"<message>\n  at <path>"`.
 * - Multiple entries are joined with newlines.
 *
 * **Example** (Formatting an issue as a string)
 *
 * ```ts
 * import { SchemaIssue } from "effect"
 *
 * const formatter = SchemaIssue.makeFormatterDefault()
 * ```
 *
 * @see {@link makeFormatterStandardSchemaV1} — produces Standard Schema V1 format instead
 * @see {@link Formatter}
 *
 * @category Formatter
 * @since 4.0.0
 */
export function makeFormatterDefault() {
  return issue => toDefaultIssues(issue, [], defaultLeafHook, defaultCheckHook).map(formatDefaultIssue).join("\n");
}
/** @internal */
export const defaultFormatter = /*#__PURE__*/makeFormatterDefault();
function formatDefaultIssue(issue) {
  let out = issue.message;
  if (issue.path && issue.path.length > 0) {
    const path = formatPath(issue.path);
    out += `\n  at ${path}`;
  }
  return out;
}
function findMessage(issue) {
  switch (issue._tag) {
    case "InvalidType":
    case "OneOf":
    case "Composite":
    case "AnyOf":
      return getMessageAnnotation(issue.ast.annotations);
    case "InvalidValue":
    case "Forbidden":
      return getMessageAnnotation(issue.annotations);
    case "MissingKey":
      return getMessageAnnotation(issue.annotations, "messageMissingKey");
    case "UnexpectedKey":
      return getMessageAnnotation(issue.ast.annotations, "messageUnexpectedKey");
    case "Filter":
      return getMessageAnnotation(issue.filter.annotations);
    case "Encoding":
      return findMessage(issue.issue);
  }
}
function getMessageAnnotation(annotations, type = "message") {
  const message = annotations?.[type];
  if (typeof message === "string") return message;
}
function formatOption(actual) {
  if (Option.isNone(actual)) return "no value provided";
  return format(actual.value);
}
/** @internal */
export function redact(issue) {
  switch (issue._tag) {
    case "MissingKey":
      return issue;
    case "Forbidden":
      return new Forbidden(Option.map(issue.actual, Redacted.make), issue.annotations);
    case "Filter":
      return new Filter(Redacted.make(issue.actual), issue.filter, redact(issue.issue));
    case "Pointer":
      return new Pointer(issue.path, redact(issue.issue));
    case "Encoding":
    case "InvalidType":
    case "InvalidValue":
    case "Composite":
      return new InvalidValue(Option.map(issue.actual, Redacted.make));
    case "AnyOf":
    case "OneOf":
    case "UnexpectedKey":
      return new InvalidValue(Option.some(Redacted.make(issue.actual)));
  }
}
//# sourceMappingURL=SchemaIssue.js.map