import type { Ref } from './flowSchema.js';

export type ParsedRef =
  | { readonly scope: 'input'; readonly port: string }
  | { readonly scope: 'output'; readonly node: string; readonly port: string }
  | { readonly scope: 'context'; readonly key: ContextKey };

export type RefScope = ParsedRef['scope'];

export type ContextKey = 'sender' | 'executionAddress';

export const isContextKey = (key: string): key is ContextKey =>
  key === 'sender' || key === 'executionAddress';

/**
 * Ref prefixes that parseRef intercepts before the fallback `output` scope.
 * A node whose id matches one of these would produce ambiguous refs
 * (e.g. `context.sender` could be a context ref or node-output ref).
 * Validation must reject these as node ids.
 */
export const RESERVED_REF_SCOPES: ReadonlySet<string> = new Set([
  'input',
  'context',
  'literal',
]);

export const isRef = (v: unknown): v is Ref =>
  typeof v === 'object' &&
  v !== null &&
  '$ref' in v &&
  typeof v.$ref === 'string';

export const parseRef = (ref: Ref): ParsedRef => {
  const dot = ref.$ref.indexOf('.');
  if (dot === -1)
    throw new Error(`Invalid ref: "${ref.$ref}" (must contain a dot)`);
  const prefix = ref.$ref.slice(0, dot);
  const suffix = ref.$ref.slice(dot + 1);
  if (prefix === 'input') return { scope: 'input', port: suffix };
  if (prefix === 'context') {
    if (!isContextKey(suffix))
      throw new Error(`Unknown context key: "${suffix}"`);
    return { scope: 'context', key: suffix };
  }
  return { scope: 'output', node: prefix, port: suffix };
};

export const foldRef = <R>(
  ref: ParsedRef,
  cases: {
    readonly input: (port: string) => R;
    readonly context: (key: ContextKey) => R;
    readonly output: (node: string, port: string) => R;
  },
): R => {
  if (ref.scope === 'input') return cases.input(ref.port);
  if (ref.scope === 'context') return cases.context(ref.key);
  return cases.output(ref.node, ref.port);
};

export const refKey = (ref: Ref): string =>
  foldRef(parseRef(ref), {
    input: (port) => `input:${port}`,
    context: (key) => `context:${key}`,
    output: (node, port) => `output:${node}:${port}`,
  });
