import { Schema } from 'effect';

import { normalizeResource } from './chainCapabilities.js';

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

// A positive integer chain id. Single source of truth on the Effect side
// (`continuation.ts` imports it; `zodSchemas.ts` mirrors it as `chainIdZod`).
export const ChainIdSchema = Schema.Number.pipe(
  Schema.int(),
  Schema.positive(),
);

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

/**
 * THE chain-capabilities chokepoint.
 *
 * `ResourceSchema` is a decode transform: it parses the plain `native | erc20`
 * wire union (`RawResourceSchema`) and then runs `normalizeResource`, so on
 * `as-erc20` chains (e.g. Tempo) a `{ kind: "native", chainId }` resource is
 * rewritten to its ERC-20 form (PathUSD) before any application code sees it.
 * Because every Resource-bearing schema embeds this one — `FlowSchema` for flow
 * inputs, op `configSchema`s for op configs — making the schema itself
 * chain-aware covers every parsed entry point in one place.
 *
 * The `encode` direction is the identity: the ERC-20 form is itself valid wire
 * format, and the rewrite is deliberately not reversible (we never want to
 * re-introduce a `native` resource when serialising). `JSONSchema.make` derives
 * the published wire format from the encoded (`from`) side, so the manifest
 * keeps advertising the plain union with both arms.
 */
export const ResourceSchema = Schema.transform(
  RawResourceSchema,
  Schema.typeSchema(RawResourceSchema),
  {
    strict: true,
    decode: (raw) => normalizeResource(raw),
    encode: (resource) => resource,
  },
);

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),
  continuations: Schema.optional(Schema.Array(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;
