import "testLib" as testLib;

def typecheckingEnabled:
  if $ENV["JQ_TYPECHECKING"] != null and (["true", "1", "debug"] | any(index($ENV["JQ_TYPECHECKING"]))) then
    true
  else false end;

def typecheckingDebug:
  if $ENV["JQ_TYPECHECKING"] == "debug" then
    true
  else false end;

def dumpType:
  if (. | type) == "object" then
    . | [to_entries | sort_by(.key)[] | {"\(.key)": (.value|dumpType)}] | add
  elif (. | type) == "array" then
    [.[] | dumpType] as $array |
    if ($array | length > 0) then
      if ($array | all(select(. == $array[0]))) then 
        [$array[0]]
      else 
        $array
      end
    else
      "array:empty/unknown"
    end
  else
    . | type
  end;

def typeErrorText:
  . as $type |
  "Type Error Checking '\($type)'";
def typeErrorText($type): $type | typeErrorText;

def typeError($type):
  if typecheckingDebug then debug({typeName: $type, value: ., valueType: . | dumpType}) end |
  error($type | typeErrorText);

def ensureNull: if typecheckingEnabled and (. | type != "null") then typeError("null") end;
def ensureBool: if typecheckingEnabled and (. | type) != "boolean" then typeError("bool") end;
def ensureString: if typecheckingEnabled and (. | type) != "string" then typeError("string") end;
def ensureArray: if typecheckingEnabled and (. | type) != "array" then typeError("array") end;
def ensureObject: if typecheckingEnabled and (. | type) != "object" then typeError("object") end;
def ensureNumber: if typecheckingEnabled and (. | type) != "number" then typeError("number") end;

def ensureNull($value): $value | ensureNull;
def ensureBool($value): $value | ensureBool;
def ensureString($value): $value | ensureString;
def ensureArray($value): $value | ensureArray;
def ensureObject($value): $value | ensureObject;
def ensureNumber($value): $value | ensureNumber;

def ensureNullOr(ensureType): 
  if . == null then . else . | ensureType end;

def ensureKey($type; $key): if (. | has($key) | not) then typeError("\($type):\($key)") end;
def ensureKey($value; $type; $key): $value | ensureKey($type; $key);

def ensureIfKey($key; ensureType): if (. | has($key)) then .[$key] | ensureType end;

def ensureWrapError($newType; ensureType):
  . as $value |
  try (. | ensureType) 
  catch (
    . as $prevError | 
    if typecheckingDebug then debug({$newType, $prevError}) end |
    error(typeErrorText($newType))
  );

def ensureWrapError($value; $newType; ensureType):
  $value | ensureWrapError($newType; ensureType);

# JQ_TYPECHECKING=true required for tests
def typeLibTests:
  testLib::expectPassed(testLib::runTest(
    "null is null";
    (null | ensureNull);
    true;
    false
  )) |
  testLib::expectPassed(testLib::runTest(
    "boolean is not null";
    (true | ensureNull);
    (. == typeErrorText("null"));
    true
  ));