import type { Field, Template, TypeNode } from "./daml-lint";

// Templates storing raw ContractId fields (or Optional/List of them) risk
// stale references: the pointed-to contract can be archived underneath.
// Exercises on_field with structured TypeNode narrowing.
// Compile: npx esbuild examples/no-bare-contractid-field.ts --bundle --outfile=examples/dist/no-bare-contractid-field.js

const NAME = "no-bare-contractid-field";
const SEVERITY = "low";
const DESCRIPTION = "Template fields holding ContractIds risk dangling references";

function containsContractId(ty: TypeNode | null): boolean {
  if (ty === null) {
    return false;
  }
  if ("App" in ty) {
    return (
      isCon(ty.App.head, "ContractId") ||
      containsContractId(ty.App.head) ||
      ty.App.args.some(containsContractId)
    );
  }
  if ("List" in ty) {
    return containsContractId(ty.List.inner);
  }
  if ("Tuple" in ty) {
    return ty.Tuple.items.some(containsContractId);
  }
  if ("Fun" in ty) {
    return containsContractId(ty.Fun.param) || containsContractId(ty.Fun.result);
  }
  if ("Constrained" in ty) {
    return containsContractId(ty.Constrained.body);
  }
  return false;
}

function isCon(ty: TypeNode, name: string): boolean {
  return "Con" in ty && ty.Con.name === name;
}

function on_field(field: Field, template: Template): void {
  if (containsContractId(field.type_)) {
    report(
      field,
      `Field '${field.name}' on template '${template.name}' stores a ContractId — consider a contract key lookup to avoid dangling references`
    );
  }
}

globalThis.__daml_lint_rule = { NAME, SEVERITY, DESCRIPTION, on_field };
