// Hover provider for the ƿit LSP.
// Shows documentation for intrinsic functions and variable info.

// Intrinsic function documentation database.
// Each entry: {signature, description}
def intrinsic_docs = {
  abs: {
    signature: "abs(number)",
    description: "Absolute value. Returns null for non-numbers."
  },
  apply: {
    signature: "apply(function, array)",
    description: "Execute the function, passing array elements as input values."
  },
  array: {
    signature: "array(value, ...)",
    description: "Create arrays. Polymorphic: array(number) creates sized array, array(array) copies, array(array, fn) maps, array(text) splits into characters, array(text, sep) splits by separator."
  },
  ceiling: {
    signature: "ceiling(number, place)",
    description: "Round up. If place is 0 or null, round to smallest integer >= number."
  },
  character: {
    signature: "character(value)",
    description: "If text, returns the first character. If a non-negative integer, returns the character from that codepoint."
  },
  codepoint: {
    signature: "codepoint(text)",
    description: "Returns the codepoint number of the first character."
  },
  ends_with: {
    signature: "ends_with(text, suffix)",
    description: "Returns true if the text ends with the given suffix."
  },
  every: {
    signature: "every(array, function)",
    description: "Returns true if every element satisfies the predicate."
  },
  extract: {
    signature: "extract(text, pattern, from, to)",
    description: "Match text to pattern. Returns a record of saved fields, or null if no match."
  },
  fallback: {
    signature: "fallback(requestor_array)",
    description: "Returns a requestor that tries each requestor in order until one succeeds."
  },
  filter: {
    signature: "filter(array, function)",
    description: "Returns a new array containing elements for which function returns true."
  },
  find: {
    signature: "find(array, function, reverse, from)",
    description: "Returns the element number where function returns true, or null if not found. If second arg is not a function, compares directly."
  },
  floor: {
    signature: "floor(number, place)",
    description: "Round down. If place is 0 or null, round to greatest integer <= number."
  },
  format: {
    signature: "format(text, collection, transformer)",
    description: "Substitute {key} placeholders in text with values from a collection (array or record)."
  },
  fraction: {
    signature: "fraction(number)",
    description: "Returns the fractional part of a number."
  },
  is_array: {
    signature: "is_array(value)",
    description: "Returns true if the value is an array."
  },
  is_blob: {
    signature: "is_blob(value)",
    description: "Returns true if the value is a blob."
  },
  is_character: {
    signature: "is_character(value)",
    description: "Returns true if the value is a single character."
  },
  is_data: {
    signature: "is_data(value)",
    description: "Returns true if the value is data (not a function)."
  },
  is_digit: {
    signature: "is_digit(value)",
    description: "Returns true if the value is a digit character."
  },
  is_false: {
    signature: "is_false(value)",
    description: "Returns true if the value is false."
  },
  is_fit: {
    signature: "is_fit(value)",
    description: "Returns true if the value is a fit integer."
  },
  is_function: {
    signature: "is_function(value)",
    description: "Returns true if the value is a function."
  },
  is_integer: {
    signature: "is_integer(value)",
    description: "Returns true if the value is an integer."
  },
  is_letter: {
    signature: "is_letter(value)",
    description: "Returns true if the value is a letter character."
  },
  is_logical: {
    signature: "is_logical(value)",
    description: "Returns true if the value is a logical (boolean)."
  },
  is_lower: {
    signature: "is_lower(value)",
    description: "Returns true if the value is a lowercase character."
  },
  is_null: {
    signature: "is_null(value)",
    description: "Returns true if the value is null."
  },
  is_number: {
    signature: "is_number(value)",
    description: "Returns true if the value is a number."
  },
  is_object: {
    signature: "is_object(value)",
    description: "Returns true if the value is an object (record)."
  },
  is_pattern: {
    signature: "is_pattern(value)",
    description: "Returns true if the value is a pattern (regex)."
  },
  is_stone: {
    signature: "is_stone(value)",
    description: "Returns true if the value is frozen (stoned)."
  },
  is_text: {
    signature: "is_text(value)",
    description: "Returns true if the value is text."
  },
  is_true: {
    signature: "is_true(value)",
    description: "Returns true if the value is true."
  },
  is_upper: {
    signature: "is_upper(value)",
    description: "Returns true if the value is an uppercase character."
  },
  is_whitespace: {
    signature: "is_whitespace(value)",
    description: "Returns true if the value is a whitespace character."
  },
  length: {
    signature: "length(value)",
    description: "Array: number of elements. Text: number of codepoints. Function: arity. Blob: number of bits. Record: record.length()."
  },
  logical: {
    signature: "logical(value)",
    description: "Convert to logical. 0/false/null/\"false\" produce false; 1/true/\"true\" produce true."
  },
  lower: {
    signature: "lower(text)",
    description: "Returns text with all uppercase characters converted to lowercase."
  },
  max: {
    signature: "max(number, number)",
    description: "Returns the larger of two numbers."
  },
  min: {
    signature: "min(number, number)",
    description: "Returns the smaller of two numbers."
  },
  modulo: {
    signature: "modulo(dividend, divisor)",
    description: "Result has the sign of the divisor."
  },
  neg: {
    signature: "neg(number)",
    description: "Negate. Reverse the sign of a number."
  },
  normalize: {
    signature: "normalize(text)",
    description: "Unicode normalize."
  },
  not: {
    signature: "not(logical)",
    description: "Returns the opposite logical. Returns null for non-logicals."
  },
  number: {
    signature: "number(value, radix_or_format)",
    description: "Convert to number. Polymorphic: number(logical), number(text), number(text, radix), number(text, format)."
  },
  parallel: {
    signature: "parallel(requestor_array, throttle, need)",
    description: "Start all requestors concurrently. Optional throttle limits concurrency; optional need specifies minimum successes."
  },
  print: {
    signature: "print(value)",
    description: "Print a value to standard output."
  },
  race: {
    signature: "race(requestor_array, throttle, need)",
    description: "Like parallel but returns as soon as needed results are obtained. Default need is 1."
  },
  record: {
    signature: "record(value, ...)",
    description: "Create records. Polymorphic: record(record) copies, record(record, record) merges, record(array) creates from keys."
  },
  reduce: {
    signature: "reduce(array, function, initial, reverse)",
    description: "Reduce an array to a single value by applying a function to pairs of elements."
  },
  remainder: {
    signature: "remainder(dividend, divisor)",
    description: "For fit integers: dividend - ((dividend // divisor) * divisor)."
  },
  replace: {
    signature: "replace(text, target, replacement, limit)",
    description: "Return text with target replaced. Target can be text or pattern. Replacement can be text or function."
  },
  reverse: {
    signature: "reverse(array)",
    description: "Returns a new array with elements in the opposite order."
  },
  round: {
    signature: "round(number, place)",
    description: "Round to nearest."
  },
  search: {
    signature: "search(text, target, from)",
    description: "Search text for target. Returns character position or null."
  },
  sequence: {
    signature: "sequence(requestor_array)",
    description: "Process requestors in order. Each result becomes input to the next."
  },
  sign: {
    signature: "sign(number)",
    description: "Returns -1, 0, or 1."
  },
  some: {
    signature: "some(array, function)",
    description: "Returns true if any element satisfies the predicate."
  },
  sort: {
    signature: "sort(array, select)",
    description: "Returns a new sorted array. Sort keys must be all numbers or all texts. Ascending and stable."
  },
  starts_with: {
    signature: "starts_with(text, prefix)",
    description: "Returns true if the text starts with the given prefix."
  },
  stone: {
    signature: "stone(value)",
    description: "Petrify the value, making it permanently immutable. Deep freeze."
  },
  text: {
    signature: "text(value, ...)",
    description: "Convert to text. Polymorphic: text(array, sep) joins, text(number, radix/format) formats, text(text, from, to) substrings."
  },
  trim: {
    signature: "trim(text, reject)",
    description: "Remove characters from both ends. Default removes whitespace."
  },
  trunc: {
    signature: "trunc(number, place)",
    description: "Truncate toward zero."
  },
  upper: {
    signature: "upper(text)",
    description: "Returns text with all lowercase characters converted to uppercase."
  },
  whole: {
    signature: "whole(number)",
    description: "Returns the whole part of a number."
  },
  meme: {
    signature: "meme()",
    description: "Create a new meme (prototype chain marker)."
  },
  proto: {
    signature: "proto(object, meme)",
    description: "Set the prototype meme of an object."
  },
  isa: {
    signature: "isa(object, meme)",
    description: "Returns true if the object has the given meme in its prototype chain."
  },
  splat: {
    signature: "splat(function, array)",
    description: "Call function with array elements as separate arguments."
  },
  use: {
    signature: "use(path)",
    description: "Load a module. Returns the module's exported value. Modules are cached and frozen."
  },
  pi: {
    signature: "pi",
    description: "An approximation of circumference / diameter: 3.1415926535897932."
  }
}

// Actor intrinsic documentation
def actor_docs = {
  "$me": {
    signature: "$me",
    description: "The address of this actor."
  },
  "$send": {
    signature: "$send(address, message)",
    description: "Send a message to another actor."
  },
  "$start": {
    signature: "$start(script, env)",
    description: "Start a new actor from a script path."
  },
  "$stop": {
    signature: "$stop()",
    description: "Stop this actor."
  },
  "$delay": {
    signature: "$delay(milliseconds)",
    description: "Delay processing for a number of milliseconds."
  },
  "$receiver": {
    signature: "$receiver(function)",
    description: "Set the message receiver function for this actor."
  },
  "$clock": {
    signature: "$clock(interval, message)",
    description: "Send a message to self at regular intervals."
  },
  "$portal": {
    signature: "$portal(name)",
    description: "Create a named portal for inter-actor communication."
  },
  "$contact": {
    signature: "$contact(portal_name)",
    description: "Connect to a named portal."
  },
  "$couple": {
    signature: "$couple(address)",
    description: "Couple with another actor for lifecycle management."
  },
  "$unneeded": {
    signature: "$unneeded(function)",
    description: "Set a function to be called when this actor is no longer needed."
  },
  "$connection": {
    signature: "$connection(address)",
    description: "Establish a connection with another actor."
  },
  "$time_limit": {
    signature: "$time_limit(milliseconds)",
    description: "Set a time limit for this actor's execution."
  }
}

// Provide hover info for a token.
var hover = function(doc, line, col, token_at) {
  var tok = token_at(doc, line, col)
  var info = null
  var name = null
  var _i = 0
  var _j = 0
  var scope = null
  var v = null

  if (tok == null) {
    return null
  }

  // Check intrinsic functions
  if (tok.kind == "name" && tok.value != null) {
    name = tok.value
    info = intrinsic_docs[name]
    if (info != null) {
      return {
        contents: {
          kind: "markdown",
          value: `**${info.signature}**\n\n${info.description}`
        }
      }
    }
  }

  // Check actor intrinsics ($name)
  if (tok.value != null && starts_with(tok.value, "$")) {
    info = actor_docs[tok.value]
    if (info != null) {
      return {
        contents: {
          kind: "markdown",
          value: `**${info.signature}**\n\n${info.description}`
        }
      }
    }
  }

  // Check keywords
  if (tok.kind == "var" || tok.kind == "def") {
    return {
      contents: {
        kind: "markdown",
        value: (tok.kind == "var")
          ? "**var** — Declare a mutable variable."
          : "**def** — Declare a constant."
      }
    }
  }

  if (tok.kind == "disrupt") {
    return {
      contents: {
        kind: "markdown",
        value: "**disrupt** — Raise an error. Use with **disruption** block to handle errors."
      }
    }
  }

  if (tok.kind == "disruption") {
    return {
      contents: {
        kind: "markdown",
        value: "**disruption** — Error handling block. Catches errors raised by **disrupt**."
      }
    }
  }

  // User variable: show declaration info from scope
  if (tok.kind == "name" && tok.value != null && doc.ast != null && doc.ast.scopes != null) {
    _i = 0
    while (_i < length(doc.ast.scopes)) {
      scope = doc.ast.scopes[_i]
      if (scope.vars != null) {
        _j = 0
        while (_j < length(scope.vars)) {
          v = scope.vars[_j]
          if (v.name == tok.value) {
            return {
              contents: {
                kind: "markdown",
                value: (v.is_const == true)
                  ? `**def** ${v.name}`
                  : `**var** ${v.name}`
              }
            }
          }
          _j = _j + 1
        }
      }
      _i = _i + 1
    }
  }

  return null
}

return {
  hover: hover,
  intrinsic_docs: intrinsic_docs,
  actor_docs: actor_docs
}
