import type { Show } from "@principia/prelude/Show";
import * as A from "../../../Array";
import { pipe } from "../../../Function";
import type { NonEmptyArray } from "../../../NonEmptyArray";
import * as O from "../../../Option";
import * as Sy from "../../../Sync";
import type { FiberId } from "../../Fiber/FiberId";
import type { Cause } from "./model";
type Segment = Sequential | Parallel | Failure;
type Step = Parallel | Failure;
interface Failure {
_tag: "Failure";
lines: string[];
}
interface Parallel {
_tag: "Parallel";
all: Sequential[];
}
interface Sequential {
_tag: "Sequential";
all: Step[];
}
const Failure = (lines: string[]): Failure => ({
_tag: "Failure",
lines
});
const Sequential = (all: Step[]): Sequential => ({
_tag: "Sequential",
all
});
const Parallel = (all: Sequential[]): Parallel => ({
_tag: "Parallel",
all
});
const headTail = (a: NonEmptyArray): [A, A[]] => {
const x = [...a];
const head = x.shift() as A;
return [head, x];
};
const lines = (s: string) => s.split("\n").map((s) => s.replace("\r", "")) as string[];
const prefixBlock = (values: readonly string[], p1: string, p2: string): string[] =>
A.isNonEmpty(values)
? pipe(headTail(values), ([head, tail]) => [`${p1}${head}`, ...tail.map((_) => `${p2}${_}`)])
: [];
const renderInterrupt = (fiberId: FiberId): Sequential =>
Sequential([Failure([`An interrupt was produced by #${fiberId.seqNumber}.`])]);
const renderError = (error: Error): string[] => lines(error.stack ? error.stack : String(error));
const renderDie = (error: Error): Sequential =>
Sequential([Failure(["An unchecked error was produced.", ...renderError(error)])]);
const renderDieUnknown = (error: string[]): Sequential =>
Sequential([Failure(["An unchecked error was produced.", ...error])]);
const renderFail = (error: string[]): Sequential =>
Sequential([Failure(["A checked error was not handled.", ...error])]);
const renderFailError = (error: Error): Sequential =>
Sequential([Failure(["A checked error was not handled.", ...renderError(error)])]);
const causeToSequential = (cause: Cause): Sy.IO =>
Sy.gen(function* (_) {
switch (cause._tag) {
case "Empty": {
return Sequential([]);
}
case "Fail": {
return cause.value instanceof Error
? renderFailError(cause.value)
: renderFail(lines(JSON.stringify(cause.value, null, 2)));
}
case "Die": {
return cause.value instanceof Error
? renderDie(cause.value)
: renderDieUnknown(lines(JSON.stringify(cause.value, null, 2)));
}
case "Interrupt": {
return renderInterrupt(cause.fiberId);
}
case "Then": {
return Sequential(yield* _(linearSegments(cause)));
}
case "Both": {
return Sequential([Parallel(yield* _(parallelSegments(cause)))]);
}
}
});
const linearSegments = (cause: Cause): Sy.IO =>
Sy.gen(function* (_) {
switch (cause._tag) {
case "Then": {
return [...(yield* _(linearSegments(cause.left))), ...(yield* _(linearSegments(cause.right)))];
}
default: {
return (yield* _(causeToSequential(cause))).all;
}
}
});
const parallelSegments = (cause: Cause): Sy.IO =>
Sy.gen(function* (_) {
switch (cause._tag) {
case "Both": {
return [...(yield* _(parallelSegments(cause.left))), ...(yield* _(parallelSegments(cause.right)))];
}
default: {
return [yield* _(causeToSequential(cause))];
}
}
});
const times = (s: string, n: number) => {
let h = "";
for (let i = 0; i < n; i += 1) {
h += s;
}
return h;
};
const format = (segment: Segment): readonly string[] => {
switch (segment._tag) {
case "Failure": {
return prefixBlock(segment.lines, "─", " ");
}
case "Parallel": {
return [
times("══╦", segment.all.length - 1) + "══╗",
...A.reduceRight_(segment.all, [] as string[], (current, acc) => [
...prefixBlock(acc, " ║", " ║"),
...prefixBlock(format(current), " ", " ")
])
];
}
case "Sequential": {
return A.chain_(segment.all, (seg) => ["║", ...prefixBlock(format(seg), "╠", "║"), "▼"]);
}
}
};
const prettyLines = (cause: Cause): Sy.IO =>
Sy.gen(function* (_) {
const s = yield* _(causeToSequential(cause));
if (s.all.length === 1 && s.all[0]._tag === "Failure") {
return s.all[0].lines;
}
return O.getOrElse_(A.updateAt(0, "╥")(format(s)), (): string[] => []);
});
export const prettyM = (cause: Cause): Sy.IO =>
Sy.gen(function* (_) {
const lines = yield* _(prettyLines(cause));
return lines.join("\n");
});
export const pretty = (cause: Cause) => Sy.runIO(prettyM(cause));
export const showCause: Show> = {
show: pretty
};