const fs = require("fs");
const glob = require("glob");
var engine = require("php-parser");
const parseComments = require("parse-docblocks");

class PhpDocParser {
  parser;
  filter;

  constructor(commentFilter) {
    this.filter = commentFilter;
    this.parser = this.createParser();
  }

  parseDocs() {
    const files = this.findPhpFiles();
    return files.flatMap((f) => this.parseFileDocs(f));
  }

  parseFileDocs(file) {
    const parsed = this.parseFile(file);
    const items = this.flattenChildren(parsed.children);
    return items.reduce((docs, c) => {
      const doc = this.createDoc(c, file);
      if (doc != null) {
        docs.push(doc);
      }
      return docs;
    }, []);
  }

  flattenChildren(children) {
    let flattened = [];
    children.forEach((child) => {
      if (child?.body?.children != null) {
        flattened = flattened.concat(this.flattenChildren(child.body.children));
      }
      if (child?.alternate?.children != null) {
        flattened = flattened.concat(this.flattenChildren(child.alternate.children));
      }
      flattened.push(child);
    });
    return flattened;
  }

  createDoc(c, file) {
    const commentStr = c.leadingComments?.pop()?.value;
    if (commentStr == null || !this.filter(commentStr)) {
      return null;
    }
    if (c.kind == "expressionstatement") {
      c = c.expression;
    }
    if (c.kind == "assign") {
      c = c.right;
    }
    return {
      file,
      line: c.loc.start.line,
      source: c.loc.source,
      name: c.name?.name ?? c.what?.name,
      comment: parseComments(commentStr),
      kind: c.kind,
      arguments: c.arguments,
    };
  }

  parseFile(file) {
    const contetnt = fs.readFileSync(file);
    return this.parser.parseCode(contetnt, file);
  }

  createParser() {
    return new engine({
      // some options :
      parser: {
        extractDoc: true,
        php7: true,
        location: true,
      },
      ast: {
        withPositions: true,
        withSource: true,
      },
      lexer: {
        all_tokens: true,
        comment_tokens: true,
        short_tags: true,
      },
    });
  }

  findPhpFiles() {
    return glob.sync("**/*.php", { ignore: "vendor/**" });
  }
}

class ReadmeWriter {
  docs = null;
  constructor(docs) {
    this.docs = docs;
  }

  writeDocs() {
    let rdLines = ["# Functions"];
    rdLines = rdLines.concat(this.docs.filter((d) => d.kind == "function").flatMap(this.getReadme));
    rdLines = rdLines.concat("# Action hooks");
    rdLines = rdLines.concat(this.docs.filter((d) => d.name == "do_action").flatMap(this.getReadme));
    rdLines = rdLines.concat("# Filter hooks");
    rdLines = rdLines.concat(this.docs.filter((d) => d.name == "apply_filters").flatMap(this.getReadme));
    fs.writeFile("./README.md", rdLines.join("\n"), (err) => {
      if (err) throw err;
      console.log("The file has been saved!");
    });
  }

  getReadme(d) {
    let lines = [];
    let name = d.name;
    if (["do_action", "apply_filters"].includes(name)) {
      name = d.arguments[0]?.value;
    }
    lines.push(`## [${name}](${d.file}#L${d.line})`);
    lines.push(d.comment?.summary);
    lines.push(d.comment?.description);
    lines.push("``` php");
    lines.push(d.source);
    lines.push("```");
    return lines;
  }
}

const phpDocsParser = new PhpDocParser((c) => c.includes("inobydoc"));
const docs = phpDocsParser.parseDocs();
const rmw = new ReadmeWriter(docs);
rmw.writeDocs();
console.log(docs);
