import { readFileSync, existsSync, writeFileSync } from "fs";

import { fileURLToPath } from "url";
import path, { dirname } from "path";
import { ensureArray } from "./common";

import { XMLParser } from "fast-xml-parser";

export interface FunctionParam {
  type: string;
  declname: string;
}

export interface DetailedParam {
  paramName: string;
  paramDescription: string;
}

export interface FunctionDetailedParamDescription {
  description: string;
  params: DetailedParam[];
  returns: string;
}

export interface FunctionDoc {
  name: string;
  definition: string;
  argsstring: string;
  param: FunctionParam[];
  briefDescription: string;
  detailedDescription: FunctionDetailedParamDescription;
  location: {
    definition: {
      file: string;
      line: number;
      column: number;
    };
    declaration: {
      file: string;
      line: number;
      column: number;
    };
  };
}

export interface TypeDefDoc {
  type: string;
  definition: string;
  argsstring: string;
  name: string;
  briefDescription: string;
  detailedDescription: string;
  location: {
    file: string;
    line: number;
    column: number;
  };
}

export interface DocFile {
  name: string;
  briefDescription: string;
  detailedDescription: string;
  functions: FunctionDoc[];
  typedefs: TypeDefDoc[];
  location: string;
}

export interface ApiDoc {
  files: DocFile[];
}

export interface MDOutput {
  filename: string;
  buffer: string;
}

export class CApiDocParser {
  private xmlParser: XMLParser;
  private buffer: string;
  private docPath: string;
  private repoPath: string;
  private fileCache: Record<string, any> = {};
  private sourceFiles: Record<string, string> = {};
  private apiDoc: ApiDoc;
  private outputs: MDOutput[] = [];

  constructor(docEntry: { buffer: string; docPath: string; repoPath: string }) {
    this.buffer = docEntry.buffer;
    this.docPath = docEntry.docPath;
    this.repoPath = docEntry.repoPath;
    this.xmlParser = new XMLParser({ ignoreAttributes: false });
    this.apiDoc = { files: [] };
  }

  private preloadAllXmlFiles(fileList: string[]) {
    for (const file of fileList) {
      this.fileCache[file] = readFileSync(path.join(this.docPath, `${file}.xml`), "utf-8");
    }
  }

  private getXmlFileNameFromRefId(refId: string): string {
    return refId;
  }

  parse() {
    const buffer = this.buffer;
    const parser = this.xmlParser;
    const obj = parser.parse(buffer);
    const compounds = obj.doxygenindex.compound;
    const importPaths = (compounds as any[])
      .filter((c) => c["@_kind"] === "file")
      .map((c) => c["@_refid"])
      .filter((refid): refid is string => typeof refid === "string");
    this.preloadAllXmlFiles(importPaths);

    for (const compound of compounds) {
      const compoundType = compound["@_kind"];
      if (compoundType === "page") continue;
      else if (compoundType === "file") {
        const file = this.parseDocFile(compound);
        this.apiDoc.files.push(file);
      }
    }
  }

  private parseDocFile(compound: any): DocFile {
    const functions: FunctionDoc[] = [];
    const typedefs: TypeDefDoc[] = [];
    const refId = compound["@_refid"];
    const fileName = this.getXmlFileNameFromRefId(refId);
    const file = this.fileCache[fileName];
    if (!file) {
      console.error(`File not found: ${fileName}`);
    }
    const parsedFile = this.xmlParser.parse(file);
    const compounddef = parsedFile?.doxygen?.compounddef;
    const compoundName = compounddef?.compoundname;
    const briefDescription = compounddef?.briefdescription || "";
    const detailedDescription = compounddef?.detaileddescription?.para || "";
    const location = compounddef?.location?.["@_file"] || "";
    const sectionDefs = ensureArray(compounddef?.sectiondef);
    for (const sectionDef of sectionDefs) {
      const sectionType = sectionDef["@_kind"];
      if (sectionType === "typedef") {
        const memberDefs = ensureArray(sectionDef?.memberdef);
        for (const memberDef of memberDefs) {
          const typedef = this.parseTypeDef(memberDef);
          typedefs.push(typedef);
        }
      } else if (sectionType === "func") {
        const memberDefs = ensureArray(sectionDef?.memberdef);
        for (const memberDef of memberDefs) {
          const func = this.parseFunction(memberDef);
          functions.push(func);
        }
      }
    }
    return {
      name: compoundName,
      briefDescription,
      detailedDescription,
      functions,
      typedefs,
      location
    };
  }

  private parseFunction(memberDef: any): FunctionDoc {
    const type = memberDef["@_kind"];
    const functionType = memberDef?.type || "";
    const definition = memberDef?.definition || "";
    const argsstring = memberDef?.argsstring || "";
    const name = memberDef?.name || "";
    const params = Array.isArray(memberDef?.param) ? memberDef?.param : [memberDef?.param];
    const parsedParams: FunctionParam[] = [];
    for (const param of params) {
      parsedParams.push(this.parseFunctionParam(param));
    }
    const briefDescription = memberDef?.briefDescription || "";
    const detailedDescription: FunctionDetailedParamDescription = this.parseFunctionDetailedDescription(
      memberDef?.detaileddescription
    );
    const location = memberDef?.location;
    const parsedLocation = {
      definition: {
        file: location?.["@_file"] || "",
        line: Number(location?.["@_line"]) || 0,
        column: Number(location?.["@_column"]) || 0
      },
      declaration: {
        file: location?.["@_declfile"] || "",
        line: Number(location?.["@_declline"]) || 0,
        column: Number(location?.["@_declcolumn"]) || 0
      }
    };
    return {
      name,
      definition,
      argsstring,
      param: parsedParams,
      briefDescription,
      detailedDescription,
      location: parsedLocation
    };
  }

  private parseFunctionParam(param: any): FunctionParam {
    return {
      type: param?.type,
      declname: param?.declname
    };
  }

  private parseFunctionDetailedDescription(description: any): FunctionDetailedParamDescription {
    let descriptionText = "";
    const params: DetailedParam[] = [];
    let returnsText = "void";

    if (description?.para) {
      const para = description.para;

      if (para["#text"]) {
        descriptionText = para["#text"];
      }

      if (para.parameterlist && para.parameterlist["@_kind"] === "param") {
        const parameterList = para.parameterlist;
        const parameterItems = ensureArray(parameterList.parameteritem);

        for (const parameterItem of parameterItems) {
          const parameterName = parameterItem?.parameternamelist?.parametername || "";
          const parameterDescription = parameterItem?.parameterdescription?.para || "";
          params.push({
            paramName: parameterName,
            paramDescription: parameterDescription
          });
        }
      }

      if (para.simplesect && para.simplesect["@_kind"] === "return") {
        returnsText = para.simplesect.para || "";
      }
    }

    return {
      description: descriptionText,
      params,
      returns: returnsText
    };
  }

  private parseTypeDef(memberDef: any): TypeDefDoc {
    const type = memberDef["@_kind"];
    const typeDef = memberDef?.type || "";
    const definition: string = memberDef?.definition || "";
    const argsstring = memberDef?.argsstring || "";
    const name = memberDef?.name || "";
    const briefDescription = memberDef?.briefdescription || "";
    const detailedDescription = memberDef?.detaileddescription?.para || "";
    const location = {
      file: memberDef?.location?.["@_file"] || "",
      line: Number(memberDef?.location?.["@_line"]) || 0,
      column: Number(memberDef?.location?.["@_column"]) || 0
    };
    return {
      type: typeDef,
      name,
      definition,
      argsstring,
      briefDescription,
      detailedDescription,
      location
    };
  }

  toMD(): MDOutput[] {
    for (const docFile of this.apiDoc.files) {
      this.writeFile(docFile);
    }
    return this.outputs;
  }

  private writeFile(docFile: DocFile) {
    let stream = "";
    stream += this.writeFrontMatter(docFile.name, docFile.briefDescription || `${docFile.name} File Reference`);
    if (docFile.typedefs.length > 0) {
      stream += "## Typedefs\n\n";
      for (const typedef of docFile.typedefs) {
        stream += this.writeTypedef(typedef);
      }
    }
    if (docFile.functions.length > 0) {
      stream += "## Functions\n\n";
      for (const func of docFile.functions) {
        stream += this.writeFunction(func);
      }
    }
    this.outputs.push({
      filename: docFile.name.replace(/\.h$/, ".md").replace(/\.c$/, ".md"),
      buffer: 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[][]): string {
    const escapedRows = rows.map((row) => row.map((cell) => this.escapeMarkdown(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 extractText(content: any): string {
    if (!content) return "";
    if (typeof content === "string") return content;
    if (typeof content === "object") {
      if ("#text" in content) return content["#text"];
      return JSON.stringify(content);
    }
    return String(content);
  }

  private extractReturnType(definition: string): string {
    const parts = definition.trim().split(/\s+/);
    if (parts.length < 2) return "";
    return parts.slice(0, -1).join(" ");
  }

  private writeFunction(func: FunctionDoc) {
    let stream = "";
    stream += "### " + func.name + "\n\n";
    if (func.definition && func.argsstring) {
      stream += "```c\n" + func.definition + func.argsstring + "\n```\n";
    }
    if (func.detailedDescription) {
      if (func.detailedDescription.description) {
        // stream += "#### 📄 Description";
        stream += "\n\n" + func.detailedDescription.description + "\n\n";
      }
      if (func.detailedDescription.params.length > 0) {
        stream += "##### Parameters\n\n";
        const params = func.detailedDescription.params.map((param) => [
          param.paramName,
          this.extractText(param.paramDescription)
        ]);
        stream += this.generateMarkdownTable(["Parameter", "Description"], params) + "\n\n";
      }

      stream += "##### ⏎ Return Value\n\n";
      if (func.detailedDescription.returns) {
        stream += "`" + this.extractReturnType(func.definition) + "`: " + func.detailedDescription.returns + "\n\n";
      }
    }
    return stream;
  }

  private writeTypedef(typedef: TypeDefDoc) {
    let stream = "";
    stream += "### " + typedef.name + "\n\n";
    if (typedef.definition) {
      stream += "```c\n" + typedef.definition + "\n```\n";
    }
    if (typedef.detailedDescription) {
      // stream += "#### 📄 Description";
      stream += "\n\n" + typedef.detailedDescription + "\n\n";
    }
    return stream;
  }

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

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

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