import { Schema } from "effect";

export const SolTypeSchema = Schema.Literal(
  "uint8",
  "uint16",
  "uint32",
  "uint64",
  "uint128",
  "uint256",
  "int128",
  "int256",
  "address",
  "bool",
  "bytes",
  "bytes4",
  "bytes32",
  "string"
);

const ChainIdSchema = Schema.Number.pipe(Schema.int(), Schema.positive());

export const ResourceSchema = Schema.Union(
  Schema.Struct({ kind: Schema.Literal("native"), chainId: ChainIdSchema }),
  Schema.Struct({
    kind: Schema.Literal("erc20"),
    token: Schema.String,
    chainId: ChainIdSchema,
  })
);

export const ResourceInputSchema = Schema.Struct({
  name: Schema.String,
  resource: ResourceSchema,
});

export const HandleInputSchema = Schema.Struct({
  name: Schema.String,
  type: SolTypeSchema,
});

export const FlowInputSchema = Schema.Union(
  ResourceInputSchema,
  HandleInputSchema
);

export const RefSchema = Schema.Struct({
  $ref: Schema.String.pipe(
    Schema.filter((s) => s.indexOf(".") > 0, {
      message: () =>
        '$ref must be a dotpath like "input.name" or "nodeId.port"',
    })
  ),
});

export const LiteralBindingSchema = Schema.Struct({
  kind: SolTypeSchema,
  value: Schema.String,
});

export const BindValueSchema = Schema.Union(RefSchema, LiteralBindingSchema);

export const AppliedGuardSchema = Schema.Struct(
  { kind: Schema.String },
  { key: Schema.String, value: Schema.Unknown }
);

export const CallSchema = Schema.Struct({
  id: Schema.String,
  op: Schema.String,
  bind: Schema.optionalWith(
    Schema.Record({ key: Schema.String, value: BindValueSchema }),
    { default: () => ({}) }
  ),
  config: Schema.optionalWith(
    Schema.Record({ key: Schema.String, value: Schema.Unknown }),
    { default: () => ({}) }
  ),
  guards: Schema.optional(Schema.Array(AppliedGuardSchema)),
});

export const ContinuationSchema = Schema.Struct({
  awaits: Schema.String,
  flowId: Schema.String,
});

export const FlowSchema = Schema.Struct({
  version: Schema.Literal(1),
  id: Schema.String,
  chainId: ChainIdSchema,
  inputs: Schema.Array(FlowInputSchema),
  nodes: Schema.Array(CallSchema),
  continuation: Schema.optional(ContinuationSchema),
});

export type SolType = typeof SolTypeSchema.Type;
export type ResourceInput = typeof ResourceInputSchema.Type;
export type HandleInput = typeof HandleInputSchema.Type;
export type FlowInput = typeof FlowInputSchema.Type;
export type Ref = typeof RefSchema.Type;
export type LiteralBinding = typeof LiteralBindingSchema.Type;
export type BindValue = typeof BindValueSchema.Type;
export type AppliedGuard = typeof AppliedGuardSchema.Type;
export type Call = typeof CallSchema.Type;
export type Continuation = typeof ContinuationSchema.Type;
export type Flow = typeof FlowSchema.Type;
