import { Effect, Schema } from "effect";

const Text = Schema.String.check(
  Schema.makeFilter((value) => value.trim().length > 0, {
    expected: "a non-empty string",
  })
);
const JsonText = Schema.String.check(
  Schema.makeFilter(
    (value) => {
      try {
        JSON.parse(value);
        return true;
      } catch {
        return false;
      }
    },
    { expected: "valid JSON text" }
  )
);

export const WorkArtifactKind = Schema.Literals([
  "definition",
  "design",
  "diff",
  "test-report",
  "verification-report",
  "runtime-log",
  "screenshot",
  "video",
  "commit",
  "branch",
  "pull-request",
  "preview",
  "deployment",
  "other",
]);
export type WorkArtifactKind = typeof WorkArtifactKind.Type;

export const ArtifactVerificationStatus = Schema.Literals([
  "unverified",
  "verified",
  "rejected",
]);
export type ArtifactVerificationStatus = typeof ArtifactVerificationStatus.Type;

export const WorkArtifactDraft = Schema.Struct({
  contentHash: Schema.optional(Text),
  environmentId: Schema.optional(Text),
  idempotencyKey: Text,
  kind: WorkArtifactKind,
  metadataJson: JsonText,
  producer: Text,
  provenanceJson: JsonText,
  sourceRevision: Schema.optional(Text),
  title: Text,
  uri: Schema.optional(Text),
  verificationStatus: ArtifactVerificationStatus,
});
export type WorkArtifactDraft = typeof WorkArtifactDraft.Type;

export const WorkDeliveryKind = Schema.Literals([
  "branch",
  "commit",
  "pull-request",
  "preview",
  "deployment",
]);
export type WorkDeliveryKind = typeof WorkDeliveryKind.Type;

export const WorkDeliveryStatus = Schema.Literals([
  "recorded",
  "ready",
  "approved",
  "delivered",
  "failed",
  "cancelled",
]);
export type WorkDeliveryStatus = typeof WorkDeliveryStatus.Type;

export const WorkDeliveryDraft = Schema.Struct({
  externalId: Text,
  idempotencyKey: Text,
  kind: WorkDeliveryKind,
  metadataJson: JsonText,
  provider: Text,
  sourceRevision: Text,
  status: WorkDeliveryStatus,
  target: Text,
  url: Schema.optional(Text),
});
export type WorkDeliveryDraft = typeof WorkDeliveryDraft.Type;

export class WorkArtifactError extends Schema.TaggedErrorClass<WorkArtifactError>()(
  "WorkArtifactError",
  { message: Schema.String }
) {}

const decode = <A, I, R>(schema: Schema.Codec<A, I, R>, input: unknown) =>
  Schema.decodeUnknownEffect(schema)(input).pipe(
    Effect.mapError(
      (cause) => new WorkArtifactError({ message: cause.message })
    )
  );

export const decodeWorkArtifactDraft = Effect.fn("Work.decodeArtifactDraft")(
  function* decodeArtifactDraft(input: unknown) {
    const draft = yield* decode(WorkArtifactDraft, input);
    if (
      draft.verificationStatus === "verified" &&
      draft.sourceRevision === undefined
    ) {
      return yield* Effect.fail(
        new WorkArtifactError({
          message: "Verified artifacts must bind an exact source revision",
        })
      );
    }
    return draft;
  }
);

export const decodeWorkDeliveryDraft = Effect.fn("Work.decodeDeliveryDraft")(
  (input: unknown) => decode(WorkDeliveryDraft, input)
);

const DELIVERY_TRANSITIONS: Readonly<
  Record<WorkDeliveryStatus, readonly WorkDeliveryStatus[]>
> = {
  approved: ["delivered", "failed", "cancelled"],
  cancelled: [],
  delivered: [],
  failed: [],
  ready: ["approved", "delivered", "failed", "cancelled"],
  recorded: ["ready", "failed", "cancelled"],
};

export const canTransitionWorkDelivery = (
  from: WorkDeliveryStatus,
  to: WorkDeliveryStatus
): boolean => DELIVERY_TRANSITIONS[from].includes(to);
