// doc.js
function docOf(obj, prop) {
  var block = obj[cell.DOC];
  if (!block) return '';

  // 1) If `block` is a string, that's the entire doc for `obj`.
  //    If a sub-property is requested, we have nowhere to look → return ''.
  if (typeof block == 'string') {
    return prop ? '' : block;
  }

  // 2) Otherwise, if `block` is an object:
  //    (a) With no `prop`, return block.doc or block[cell.DOC].
  //    (b) If `prop` is given, look for doc specifically for that property (just one level).
  if (typeof block == 'object') {
    // 2a) No property → top-level doc
    if (!prop) {
      if (typeof block.doc == 'string') {
        return block.doc;
      }
      if (typeof block[cell.DOC] == 'string') {
        return block[cell.DOC];
      }
      return '';
    }

    // 2b) If a prop is requested → see if there's a doc string or object for that property
    var subBlock = block[prop];
    if (!subBlock) return '';
    if (typeof subBlock == 'string') {
      return subBlock;
    }
    if (typeof subBlock == 'object') {
      if (typeof subBlock.doc == 'string') {
        return subBlock.doc;
      }
      if (typeof subBlock[cell.DOC] == 'string') {
        return subBlock[cell.DOC];
      }
      return '';
    }
  }

  // If nothing matched, return empty.
  return '';
}

function parseDocStr(docStr) {
  if (!docStr) return [];

  var lines = docStr.split('\n');
  var paramRe = /^:param\s+([A-Za-z0-9_]+)\s*:\s*(.*)$/;
  var returnRe = /^:return:\s*(.*)$/;

  var descLines = [];
  var paramLines = [];
  var returnLines = [];

  for (var i = 0; i < lines.length; i++) {
    var line = lines[i].trim();
    if (!line) {
      // Keep empty lines in the desc section
      descLines.push('');
      continue;
    }

    var pm = paramRe.exec(line);
    var rm = returnRe.exec(line);

    if (pm) {
      paramLines.push('**' + pm[1] + '**: ' + pm[2]);
      paramLines.push('');
    } else if (rm) {
      returnLines.push('**Returns**: ' + rm[1]);
      returnLines.push('');
    } else {
      descLines.push(line);
    }
  }

  var final = [];

  // Reassemble: description → params → returns
  if (descLines.length) {
    final.push.apply(final, descLines);
    if (paramLines.length || returnLines.length) {
      final.push('');
    }
  }

  if (paramLines.length) {
    final.push.apply(final, paramLines);
  }

  if (returnLines.length) {
    // If there were param lines, ensure blank line before the returns
    if (paramLines.length && returnLines[0] != '') {
      final.push('');
    }
    final.push.apply(final, returnLines);
  }

  return final;
}

function writeDocFile(obj, title) {
  var lines = [];
  var docTitle = title || "Documentation";

  lines.push('# ' + docTitle + '\n');
  walkObject(obj, lines, 1, docTitle);

  return lines.join('\n');
  // If you had I/O, you'd write to file here
}

/**
 * Documents the top-level object and each of its immediate properties,
 * but does NOT recurse deeper than that.
 */
function walkObject(obj, lines, level, name) {
  // Print top-level doc or fallback heading
  var topDocStr = docOf(obj);
  if (topDocStr) {
    var docOut = parseDocStr(topDocStr);
    if (docOut.length > 0) {
      lines.push(docOut.join('\n') + '\n');
    }
  }

  var propNames = Object.getOwnPropertyNames(obj);
  for (var i = 0; i < propNames.length; i++) {
    var prop = propNames[i];
    if (prop == 'constructor') continue;

    var desc = Object.getOwnPropertyDescriptor(obj, prop);
    if (!desc) continue;

    // Check if accessor property (getter/setter)
    var hasGetter = typeof desc.get == 'function';
    var hasSetter = typeof desc.set == 'function';

    if (hasGetter || hasSetter) {
      writeProperty(lines, obj, prop, desc, level);
      continue;
    }

    // If it's a normal data property
    if ('value' in desc) {
      var val = desc.value;

      // If it's a function, treat it like a method
      if (typeof val == 'function') {
        writeMethod(lines, obj, prop, val, level);
      }
      // If it's an object, just print doc for that object (no deep recursion)
      else if (val && typeof val == 'object') {
        writeSubObject(lines, obj, prop, val, level);
      }
      // Otherwise, it's a primitive or something else
      else {
        writeDataProperty(lines, obj, prop, val, level);
      }
    }
  }
}

/**
 * Write out a heading and doc for an object property
 * (but do NOT recurse into that object's subproperties).
 */
function writeSubObject(lines, parentObj, prop, val, level) {
  // E.g. "## someObject <sub>object</sub>"
  var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>object</sub>';
  lines.push(heading + '\n');

  // 1) If there's doc assigned specifically to the parent's doc block for `prop`
  var docStr = docOf(parentObj, prop);
  // 2) If no doc there, see if the object itself has top-level doc
  if (!docStr) {
    docStr = docOf(val);
  }

  var docOut = parseDocStr(docStr);
  if (docOut.length > 0) {
    lines.push(docOut.join('\n') + '\n');
  }
}

function writeProperty(lines, parentObj, prop, desc, level) {
  // E.g. "## myProp <sub>accessor</sub>"
  var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>accessor</sub>';
  lines.push(heading + '\n');

  var hasGetter = typeof desc.get == 'function';
  var hasSetter = typeof desc.set == 'function';

  if (hasGetter && !hasSetter) {
    lines.push('(read only)\n');
  } else if (!hasGetter && hasSetter) {
    lines.push('(set only)\n');
  }

  // Look up doc in parent's doc block first, or in the accessor functions
  var docStr = docOf(parentObj, prop);
  if (!docStr && hasGetter) {
    docStr = docOf(desc.get) || docStr;
  }
  if (!docStr && hasSetter) {
    docStr = docOf(desc.set) || docStr;
  }

  var docOut = parseDocStr(docStr);
  if (docOut.length > 0) {
    lines.push(docOut.join('\n') + '\n');
  }
}

function writeMethod(lines, parentObj, prop, fn, level) {
  // Pull doc from the function or from the parent's doc block
  var docStr = fn[cell.DOC] || docOf(parentObj, prop) || '';
  var paramNames = [];

  // Try to extract param names from the doc
  if (docStr) {
    var docLines = docStr.split('\n');
    var paramRe = /^:param\s+([A-Za-z0-9_]+)\s*:\s*(.*)$/;
    for (var j = 0; j < docLines.length; j++) {
      var pm = paramRe.exec(docLines[j]);
      if (pm) paramNames.push(pm[1]);
    }
  }

  // E.g. "## myFunction(param1, param2) <sub>function</sub>"
  var heading = '#'.repeat(level + 2) + ' ' + prop;

  if (paramNames.length > 0) {
    heading += '(' + paramNames.join(', ') + ')';
  } else {
    // As a fallback, parse function signature
    var m = fn.toString().match(/\(([^)]*)\)/);
    if (m && m[1].trim()) {
      heading += '(' + m[1].trim() + ')';
    } else {
      // If no params at all (or we cannot detect them), still add ()
      heading += '()';
    }
  }

  heading += ' <sub>function</sub>';
  lines.push(heading + '\n');

  var docOut = parseDocStr(docStr);
  if (docOut.length > 0) {
    lines.push(docOut.join('\n') + '\n');
  }
}

/**
 * Write out a heading and doc for a primitive (string, number, boolean, etc).
 */
function writeDataProperty(lines, parentObj, prop, val, level) {
  // E.g. "## someString <sub>string</sub>"
  var typeStr = typeof val;
  var heading = '#'.repeat(level + 2) + ' ' + prop + ' <sub>' + typeStr + '</sub>';
  lines.push(heading + '\n');

  // Look up doc in parent's doc block
  var docStr = docOf(parentObj, prop);
  var docOut = parseDocStr(docStr);
  if (docOut.length > 0) {
    lines.push(docOut.join('\n') + '\n');
  }
}

// Provide a doc string for writeDocFile
writeDocFile[cell.DOC] = `Return a markdown string for a given obj, with an optional title.`;

var doc = {writeDocFile: writeDocFile}
doc[cell.DOC] = `
Provides a consistent way to create documentation for prosperon elements. Objects are documented by adding docstrings directly to object-like things (functions, objects, ...), or to an object's own "doc object".

Docstrings are set to the symbol \`cell.DOC\`

\`\`\`js
// Suppose we have a module that returns a function
function greet(name) { log.console("Hello, " + name) }

// We can attach a docstring
greet.doc = \`
Greets the user by name.
:param name: The name of the person to greet.
\`

// A single function is a valid return!
return greet
\`\`\`

\`\`\`js
// Another way is to add a docstring object to an object
var greet = {
  hello() { log.console('hello!') }
}

greet[cell.DOC] = {}
greet[cell.DOC][cell.DOC] = 'An object full of different greeter functions'
greet[cell.DOC].hello = 'A greeter that says, "hello!"'
\`\`\`
`

return doc
