import type { MDOutput } from "./common";

export interface CommandArg {
  arg: {
    name: string;
    values: string[];
    description: string;
  };
}

export interface CommandGroup {
  preamble: string; // the base command keyword
  mainFunc: string; // the function that handles this command
  subFuncs: string[]; // the subfunction that handles this command
  syn: string[]; // different valid syntax patterns for the command
  desc: string; // command description
  args: CommandArg[]; // detailed argument descriptions for each syntax
  examples: string[]; // usage examples for the command
  tags: string[]; // command category tag(s)
}

const githubFunctionUrl = "https://git.jabber.space/devs/profanity/src/branch/master/src/command/cmd_defs.c";

export class CommandParser {
  private buffer: string;
  private readonly preamblePattern: RegExp =
    /CMD_PREAMBLE\(\s*"([^"]+)"\s*,\s*([\w_]+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*([^)]+)\)/ms;
  private readonly mainFuncPattern: RegExp = /CMD_MAINFUNC\((\w+)\)/;
  private readonly subFuncPattern: RegExp = /CMD_SUBFUNC\((\w+)\)/;
  private readonly tagsPattern: RegExp = /CMD_TAGS\(([^)]+)\)/;
  private readonly synPattern: RegExp = /CMD_SYN\(\s*([\s\S]*?[^\\]\")\s*\)/m;
  private readonly descPattern: RegExp = /CMD_DESC\(([\s\S]*?[^\\]\")\s*\)/m;
  private readonly argsPattern: RegExp = /CMD_ARGS\(([\s\S]*?)\)/m;
  private readonly examplesPattern: RegExp = /CMD_EXAMPLES\(([\s\S]*?)\)/m;
  private commands: CommandGroup[] = [];

  constructor(buffer: string) {
    this.buffer = buffer;
    this.parse();
  }

  private parse() {
    const buffer = this.buffer;
    const commandDefsMatch = buffer.match(/command_defs\[\]\s*=\s*\{([\s\S]*)\};/);

    if (!commandDefsMatch) {
      console.error("Could not find command_defs[]");
      process.exit(1);
    }

    const commandDefsContent = commandDefsMatch[1];
    const commandBlocks = this.extractBlocks(commandDefsContent);
    for (const block of commandBlocks) {
      const group = this.parseBlock(block);
      this.commands.push(group);
    }
  }

  private extractBlocks(content: string) {
    const blocks: string[] = [];
    let pos = 0;
    const len = content.length;
    while (pos < len) {
      const start = content.indexOf("{", pos);
      if (start === -1) break;
      let braceCount = 1;
      let end = start + 1;
      while (end < len && braceCount > 0) {
        if (content[end] === "{") {
          braceCount++;
        } else if (content[end] === "}") {
          braceCount--;
        }
        end++;
      }
      if (braceCount === 0) {
        const block = content.slice(start, end);
        blocks.push(block.trim());
        pos = end;
      } else {
        break;
      }
    }
    return blocks;
  }

  private extractMultilineStrings(input: string): string[] {
    const regex = /"((?:[^"\\]|\\.)*)"/g;
    const matches = [];
    let match;
    while ((match = regex.exec(input)) !== null) {
      matches.push(match[1].replace(/\\"/g, '"'));
    }
    return matches;
  }

  private parseArgNameAndValues(argStr: string): {
    name: string;
    values: string[];
  } {
    return {
      name: argStr,
      values: []
    };
  }

  private extractArgs = (input: string): CommandArg[] => {
    const matches = [...input.matchAll(/\{\s*"([^"]+?)",\s*"([^"]+?)"\s*\}/gs)];
    return matches.map((m) => {
      const rawArg = m[1];
      const description = m[2];
      const { name, values } = this.parseArgNameAndValues(rawArg);
      return {
        arg: {
          name,
          values,
          description
        }
      };
    });
  };
  private parseBlock(block: string): CommandGroup {
    const preambleMatch = block.match(this.preamblePattern);
    const mainFuncMatch = block.match(this.mainFuncPattern);
    const subFuncMatch = block.match(this.subFuncPattern);
    const tagsMatch = block.match(this.tagsPattern);
    const synMatch = block.match(this.synPattern);
    const descMatch = block.match(this.descPattern);
    const argsMatch = block.match(this.argsPattern);
    const examplesMatch = block.match(this.examplesPattern);

    return {
      preamble: preambleMatch?.[1] || "",
      mainFunc: mainFuncMatch?.[1] || "",
      subFuncs: subFuncMatch?.[1].split(",") || [],
      syn: synMatch ? this.extractMultilineStrings(synMatch[1]) : [],

      desc: descMatch ? this.extractMultilineStrings(descMatch[1]).join(" ") : "",
      args: argsMatch ? this.extractArgs(argsMatch[1]) : [],
      examples: examplesMatch ? this.extractMultilineStrings(examplesMatch[1]) : [],
      tags: tagsMatch ? [...tagsMatch[1].matchAll(/CMD_TAG_([A-Z_]+)/g)].map((m) => m[1]?.toLowerCase()) : []
    };
  }

  private capitalizeFirstLetter(str: string) {
    if (!str) return "";
    if (str.charAt(0) === "/") {
      if (str.length > 1) {
        return str.charAt(0) + str.charAt(1).toUpperCase() + str.slice(2);
      } else {
        return str;
      }
    } else {
      return str.charAt(0).toUpperCase() + str.slice(1);
    }
  }

  toMD() {
    let stream = "";
    const output: MDOutput = { filename: "command-reference.md", buffer: "" };
    stream += this.writeFrontMatter("Command Reference", "Command Reference");

    // Sort commands alphabetically by command name
    this.commands = this.commands.sort((a, b) => { if (a.preamble < b.preamble) return -1; if (a.preamble > b.preamble) return 1; return 0; });

    for (const group of this.commands) {
      if (group.preamble) {
        stream += this.writeCommandHeader(group.preamble);
      } else {
        console.error("No preamble for group", group);
        process.exit(1);
      }
      if (group.desc) {
        // No need for title, it's obvious that it's a description (right under the command's title)
        // stream += this.writeSection("📄 Description", true);
        stream += group.desc;
        stream += "\n\n";
      }
      if (group.syn) {
        stream += this.writeSynopsisBlock(group.syn);
      }
      if (group.args.length > 0) {
        stream += this.writeArgsBlock(group.args);
      }
      if (group.examples.length > 0) {
        stream += this.writeExamplesBlock(group.examples);
      }
      stream += "\n\n";
    }

    output.buffer = stream;
    return output;
  }

  private beginBlock() {
    return `---\n`;
  }

  private endBlock() {
    return `---\n\n`;
  }

  private writeSection(sectionName: string, isChild = false) {
    return `${isChild ? "####" : "##"} ${!isChild ? "`" : ""}${sectionName}${!isChild ? "`" : ""}\n\n`;
  }

  private writeGenericBlock(blockName: string, content: string) {
    return `**${blockName}** ${content}<br/>\n`;
  }

  private writeSynopsisBlock(syntaxList: string[]) {
    let stream = "";
    // No need for Synopsis section, it's self-explanatory
    // stream += this.writeSection("🔧 Synopsis", true);
    stream += "```bash\n";
    stream += syntaxList.join("\n");
    stream += "\n";
    stream += "```\n\n";
    return stream;
  }

  private writeExamplesBlock(exampleList: string[]) {
    let stream = "";
    stream += this.writeSection("💡 Examples", true);
    stream += "```bash\n";
    stream += exampleList.join("\n");
    stream += "\n";
    stream += "```";
    stream += "\n\n";
    return stream;
  }

  private escapeMarkdown(text: string): string {
    return text
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/\|/g, "&#124;")
      .replace(/\[/g, "&#91;")
      .replace(/\]/g, "&#93;");
  }

  private generateMarkdownTable(headers: string[], rows: string[][], escapeMarkdownForRows=true): string {
    const escapedRows = rows.map((row) => row.map((cell) => escapeMarkdownForRows ? this.escapeMarkdown(cell) : cell));
    const escapedHeaders = headers.map((header) => this.escapeMarkdown(header));
    const colWidths = headers.map((_, i) =>
      Math.max(escapedHeaders[i].length, ...escapedRows.map((row) => (row[i] || "").length))
    );
    const pad = (text: string, width: number) => text + " ".repeat(width - text.length);

    const lines = [];
    lines.push("| " + escapedHeaders.map((h, i) => pad(h, colWidths[i])).join(" | ") + " |");
    lines.push("| " + colWidths.map((w) => "-".repeat(w)).join(" | ") + " |");

    for (const row of escapedRows) {
      lines.push("| " + row.map((cell, i) => pad(cell, colWidths[i])).join(" | ") + " |");
    }

    return lines.join("\n");
  }

  private writeArgsBlock(args: CommandArg[]) {
    let stream = "";
    stream += this.writeSection("📝 Arguments", true);
    const headers = ["Argument", "Description"];
    const rows = args.map((arg) => [`\`${arg.arg.name}\``, this.escapeMarkdown(arg.arg.description)]);
    stream += this.generateMarkdownTable(headers, rows, false);
    stream += "\n\n";
    return stream;
  }

  private writeFrontMatter(title: string, description: string) {
    return `${this.beginBlock()}title: ${title}\ndescription: ${description || title
      }\ntableOfContents:\n  minHeadingLevel: 1\n  maxHeadingLevel: 2\n${this.endBlock()}`;
  }

  private writeCommandHeader(title: string) {
    return this.writeSection(title);
  }
}
