import { Cause, Data, Effect, Predicate, Result } from "effect";
import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";

import { captureCause, captureCauseEffect } from "../observability";

// Implements `Respondable` so the framework's default cause→response
// pipeline (`HttpServerRespondable.toResponseOrElse`) renders this as the
// declared JSON body + status code without an explicit `catchCause` at
// every error boundary.
export class HttpResponseError extends Data.TaggedError("HttpResponseError")<{
  readonly status: number;
  readonly code: string;
  readonly message: string;
}> {
  [HttpServerRespondable.symbol](): Effect.Effect<HttpServerResponse.HttpServerResponse> {
    const self = this;
    return Effect.gen(function* () {
      if (self.status >= 500) yield* captureCauseEffect(self);
      return HttpServerResponse.jsonUnsafe(
        { error: self.message, code: self.code },
        { status: self.status },
      );
    });
  }
}

const unwrapCause = (error: unknown): unknown => {
  if (!Cause.isCause(error)) return error;

  const failure = Cause.findError(error);
  if (Result.isSuccess(failure)) return failure.success;

  const defect = Cause.findDefect(error);
  if (Result.isSuccess(defect)) return defect.success;

  return error;
};

const isHttpResponseError = (error: unknown): error is HttpResponseError =>
  Predicate.isTagged(error, "HttpResponseError");

const toHttpResponseError = (error: unknown): HttpResponseError => {
  const unwrapped = unwrapCause(error);
  return isHttpResponseError(unwrapped)
    ? unwrapped
    : new HttpResponseError({
        status: 500,
        code: "internal_server_error",
        message: "Internal server error",
      });
};

export const isServerError = (error: unknown): boolean => toHttpResponseError(error).status >= 500;

export const toErrorResponse = (error: unknown): Response => {
  const mapped = toHttpResponseError(error);
  if (mapped.status >= 500) captureCause(mapped);
  return Response.json({ error: mapped.message, code: mapped.code }, { status: mapped.status });
};

export const toErrorServerResponse = (error: unknown): HttpServerResponse.HttpServerResponse => {
  const mapped = toHttpResponseError(error);
  if (mapped.status >= 500) captureCause(mapped);
  return HttpServerResponse.jsonUnsafe(
    { error: mapped.message, code: mapped.code },
    { status: mapped.status },
  );
};

export const toErrorServerResponseEffect = (
  error: unknown,
): Effect.Effect<HttpServerResponse.HttpServerResponse> =>
  Effect.gen(function* () {
    const mapped = toHttpResponseError(error);
    if (mapped.status >= 500) yield* captureCauseEffect(mapped);
    return HttpServerResponse.jsonUnsafe(
      { error: mapped.message, code: mapped.code },
      { status: mapped.status },
    );
  });
