import { Effect, Schema } from "effect";

const Text = Schema.String.check(
  Schema.makeFilter((value) => value.trim().length > 0, {
    expected: "a non-empty string",
  })
);

export const ImpactMap = Schema.Struct({
  files: Schema.Array(Text),
  modules: Schema.Array(Text),
  risks: Schema.Array(Text),
  summary: Text,
});
export type ImpactMap = typeof ImpactMap.Type;

export const VerticalSlice = Schema.Struct({
  codeBoundaries: Schema.Array(Text),
  dependsOn: Schema.Array(Text),
  evidenceRequirements: Schema.NonEmptyArray(Text),
  id: Text,
  objective: Text,
  observableBehavior: Text,
  reviewRequired: Schema.Boolean,
  title: Text,
  verification: Schema.NonEmptyArray(Text),
});
export type VerticalSlice = typeof VerticalSlice.Type;

export const DesignPacket = Schema.Struct({
  architectureSummary: Text,
  callFlowDelta: Schema.Array(Text),
  concerns: Schema.Array(Text),
  definitionVersion: Schema.Int,
  evidenceRequirements: Schema.NonEmptyArray(Text),
  fileTreeDelta: Schema.Array(Text),
  impactMap: ImpactMap,
  invariants: Schema.NonEmptyArray(Text),
  keyInterfaces: Schema.Array(Text),
  slices: Schema.Array(VerticalSlice),
  tradeoffs: Schema.Array(Text),
  version: Schema.Int,
});
export type DesignPacket = typeof DesignPacket.Type;

export const DesignApproval = Schema.Struct({
  approvedAt: Schema.Number,
  approvedBy: Text,
  definitionVersion: Schema.Int,
  designVersion: Schema.Int,
});
export type DesignApproval = typeof DesignApproval.Type;

export class DesignError extends Schema.TaggedErrorClass<DesignError>()(
  "DesignError",
  {
    message: Schema.String,
    reason: Schema.Literals([
      "Invalid",
      "NoObservableSlices",
      "DefinitionMismatch",
      "InvalidSliceGraph",
    ]),
  }
) {}

export const validateDesignPacket = Effect.fn("Work.validateDesignPacket")(
  function* validateDesignPacket(input: unknown) {
    const design = yield* Schema.decodeUnknownEffect(DesignPacket)(input).pipe(
      Effect.mapError(
        (cause) =>
          new DesignError({ message: cause.message, reason: "Invalid" })
      )
    );
    if (
      design.slices.length === 0 ||
      design.slices.length > 4 ||
      design.slices.some(
        (slice) =>
          slice.codeBoundaries.length === 0 ||
          slice.observableBehavior.trim().length === 0 ||
          slice.evidenceRequirements.length === 0 ||
          slice.verification.length === 0
      )
    ) {
      return yield* Effect.fail(
        new DesignError({
          message:
            "Every Design must contain observable, independently verifiable slices",
          reason: "NoObservableSlices",
        })
      );
    }
    const seen = new Set<string>();
    for (const slice of design.slices) {
      if (
        seen.has(slice.id) ||
        slice.dependsOn.some(
          (dependency) => dependency === slice.id || !seen.has(dependency)
        )
      ) {
        return yield* Effect.fail(
          new DesignError({
            message:
              "Slice IDs must be unique and dependencies must reference earlier slices",
            reason: "InvalidSliceGraph",
          })
        );
      }
      seen.add(slice.id);
    }
    return design;
  }
);

export const isObservableSlice = (slice: VerticalSlice): boolean =>
  slice.observableBehavior.trim().length > 0 &&
  slice.evidenceRequirements.length > 0 &&
  slice.verification.length > 0;
