import type { HandleInput, Ref, ResourceInput, SolType } from "./flowSchema.js";
import type {
  Port,
  ResourcePort,
  HandlePort,
  ResourceOutputPort,
} from "./flow.js";
import type { Availability } from "./resource.js";
import { erc20Resource, nativeResource } from "./resource.js";
import type { HandleUnits } from "./zodSchemas.js";

export interface OpHandleOptions {
  readonly expose?: boolean;
  readonly units?: HandleUnits;
}

export const inputRef = (port: string): Ref => ({ $ref: `input.${port}` });

export const outputRef = (node: string, port: string): Ref => ({
  $ref: `${node}.${port}`,
});

/** Returns `{ [key]: value }` when `value` is not nullish, else `{}`. */
const optionalProp = <K extends string, V>(
  key: K,
  value: V | undefined | null
): Partial<Record<K, V>> =>
  value != null ? ({ [key]: value } as Partial<Record<K, V>>) : {};

export const linear = <N extends string, T extends SolType>(
  name: N,
  type: T
): Port & { readonly name: N; readonly type: T; readonly mode: "linear" } =>
  ({ name, type, mode: "linear", availability: "now" } as Port & {
    readonly name: N;
    readonly type: T;
    readonly mode: "linear";
  });

export const copy = <N extends string, T extends SolType>(
  name: N,
  type: T
): Port & { readonly name: N; readonly type: T; readonly mode: "copy" } =>
  ({ name, type, mode: "copy", availability: "now" } as Port & {
    readonly name: N;
    readonly type: T;
    readonly mode: "copy";
  });

export const handle = <N extends string, T extends SolType>(
  name: N,
  type: T
): HandleInput & { readonly name: N; readonly type: T } =>
  ({ name, type } as HandleInput & { readonly name: N; readonly type: T });

export const native = <N extends string>(
  name: N,
  chainId: number
): ResourceInput & { readonly name: N } =>
  ({ name, resource: nativeResource(chainId) } as ResourceInput & {
    readonly name: N;
  });

export const erc20 = <N extends string>(
  name: N,
  token: string,
  chainId: number
): ResourceInput & { readonly name: N } =>
  ({ name, resource: erc20Resource(token, chainId) } as ResourceInput & {
    readonly name: N;
  });

export const resource = <N extends string, Opt extends boolean = false>(
  name: N,
  accepts: "erc20" | "native" | "any",
  options?: { optional?: Opt }
): ResourcePort & { readonly name: N; readonly optional: Opt } =>
  ({
    kind: "resource",
    name,
    accepts,
    mode: "linear",
    ...optionalProp("optional", options?.optional),
  } as ResourcePort & { readonly name: N; readonly optional: Opt });

export const readResource = <N extends string>(
  name: N,
  accepts: "erc20" | "native" | "any"
): ResourcePort & { readonly name: N; readonly mode: "copy" } =>
  ({ kind: "resource", name, accepts, mode: "copy" } as ResourcePort & {
    readonly name: N;
    readonly mode: "copy";
  });

export const opHandle = <N extends string, T extends SolType>(
  name: N,
  type: T,
  options?: OpHandleOptions
): HandlePort & { readonly name: N; readonly type: T } =>
  ({
    kind: "handle",
    name,
    type,
    mode: "copy",
    ...optionalProp("expose", options?.expose),
    ...optionalProp("units", options?.units),
  } as HandlePort & {
    readonly name: N;
    readonly type: T;
  });

export const resourceOutput = <N extends string>(
  name: N,
  options?:
    | Availability
    | {
        availability?: Availability;
        providesMinimum?: boolean;
        omitIfZero?: boolean;
        deliveryAddressInput?: string;
      }
): ResourceOutputPort & { readonly name: N } => {
  const opts =
    typeof options === "string" ? { availability: options } : options;
  return {
    kind: "resource_output",
    name,
    mode: "linear",
    ...optionalProp("availability", opts?.availability),
    ...optionalProp("providesMinimum", opts?.providesMinimum),
    ...optionalProp("omitIfZero", opts?.omitIfZero),
    ...optionalProp("deliveryAddressInput", opts?.deliveryAddressInput),
  } as ResourceOutputPort & { readonly name: N };
};
