#!/usr/bin/env node
// Enhanced TypeScript/JavaScript AST parser
// Usage: node parse_ts.js file.ts

const fs = require("fs");
const path = require("path");
const { parse } = require("@typescript-eslint/typescript-estree");

function getLoc(node) {
  if (node.loc) {
    return { start: node.loc.start.line, end: node.loc.end.line };
  }
  return { start: 1, end: 1 };
}

function extractLeadingComment(node, sourceCode) {
  if (!node.range || !sourceCode) return "";

  const startIdx = node.range[0];

  // Find the start of the line containing the node
  let lineStart = startIdx;
  while (lineStart > 0 && sourceCode[lineStart - 1] !== "\n") {
    lineStart--;
  }

  // Check for inline comment on the same line
  const lineContent = sourceCode.slice(lineStart, startIdx).trim();
  if (lineContent.startsWith("//")) {
    return lineContent.substring(2).trim();
  }

  // Look backward through previous lines for comments
  const lines = sourceCode.substring(0, lineStart).split("\n");
  const commentLines = [];
  let inBlockComment = false;

  for (let i = lines.length - 1; i >= Math.max(0, lines.length - 10); i--) {
    const line = lines[i].trim();

    if (line.endsWith("*/")) {
      // End of block comment found
      inBlockComment = true;
      const blockContent = line
        .slice(0, -2)
        .replace(/^\*+\s*/, "")
        .trim();
      if (blockContent) {
        commentLines.unshift(blockContent);
      }
    } else if (inBlockComment) {
      if (line.startsWith("/*") || line.startsWith("/**")) {
        // Start of block comment found
        const blockContent = line
          .substring(line.indexOf("/*") + 2)
          .replace(/^\*+\s*/, "")
          .trim();
        if (blockContent) {
          commentLines.unshift(blockContent);
        }
        break;
      } else {
        // Inside block comment
        const blockContent = line.replace(/^\*+\s*/, "").trim();
        if (blockContent) {
          commentLines.unshift(blockContent);
        }
      }
    } else if (line.startsWith("//")) {
      // Single-line comment
      commentLines.unshift(line.substring(2).trim());
    } else if (line === "") {
      // Empty line - continue if we already have comments
      if (commentLines.length === 0) {
        break;
      }
    } else {
      // Non-comment, non-empty line - stop looking
      break;
    }
  }

  return commentLines.join("\n").trim();
}

function extractTypeInfo(node) {
  const typeInfo = {};

  // Extract type annotation
  if (node.typeAnnotation) {
    typeInfo.returnType = extractTypeString(node.typeAnnotation.typeAnnotation);
  }

  // Extract parameters for functions
  if (node.params) {
    typeInfo.parameters = node.params.map((param) => {
      const paramInfo = {
        name: extractParamName(param),
        type: param.typeAnnotation
          ? extractTypeString(param.typeAnnotation.typeAnnotation)
          : "any",
      };
      if (param.optional) {
        paramInfo.optional = true;
      }
      return paramInfo;
    });
  }

  // Extract generics
  if (node.typeParameters) {
    typeInfo.generics = node.typeParameters.params.map((tp) => tp.name.name);
  }

  return typeInfo;
}

function extractParamName(param) {
  if (param.type === "Identifier") {
    return param.name;
  }
  if (param.type === "RestElement" && param.argument.type === "Identifier") {
    return "..." + param.argument.name;
  }
  if (param.type === "AssignmentPattern" && param.left.type === "Identifier") {
    return param.left.name;
  }
  return "unknown";
}

function extractTypeString(typeNode) {
  if (!typeNode) return "any";

  switch (typeNode.type) {
    case "TSStringKeyword":
      return "string";
    case "TSNumberKeyword":
      return "number";
    case "TSBooleanKeyword":
      return "boolean";
    case "TSAnyKeyword":
      return "any";
    case "TSVoidKeyword":
      return "void";
    case "TSUndefinedKeyword":
      return "undefined";
    case "TSNullKeyword":
      return "null";
    case "TSTypeReference":
      if (typeNode.typeName.type === "Identifier") {
        let typeStr = typeNode.typeName.name;
        if (typeNode.typeParameters) {
          const params = typeNode.typeParameters.params
            .map(extractTypeString)
            .join(", ");
          typeStr += `<${params}>`;
        }
        return typeStr;
      }
      return "unknown";
    case "TSArrayType":
      return extractTypeString(typeNode.elementType) + "[]";
    case "TSUnionType":
      return typeNode.types.map(extractTypeString).join(" | ");
    case "TSIntersectionType":
      return typeNode.types.map(extractTypeString).join(" & ");
    case "TSFunctionType":
      return "Function";
    case "TSTypeLiteral":
      return "object";
    default:
      return "unknown";
  }
}

function extractDeclarations(ast, sourceCode) {
  const decls = [];
  const parentMap = new WeakMap();

  function setParents(node, parent = null) {
    if (!node || typeof node !== "object") return;

    if (Array.isArray(node)) {
      node.forEach((n) => setParents(n, parent));
      return;
    }

    parentMap.set(node, parent);
    Object.values(node).forEach((child) => setParents(child, node));
  }

  setParents(ast);

  function visit(node) {
    if (!node || typeof node !== "object") return;

    if (Array.isArray(node)) {
      node.forEach((n) => visit(n));
      return;
    }

    const docComment = extractLeadingComment(node, sourceCode);
    const loc = getLoc(node);

    // Function Declarations
    if (node.type === "FunctionDeclaration" && node.id?.name) {
      const typeInfo = extractTypeInfo(node);

      decls.push({
        name: node.id.name,
        type: "function",
        doc_comment: docComment,
        start_line: loc.start,
        end_line: loc.end,
        is_async: node.async || false,
        is_generator: node.generator || false,
        ...typeInfo,
      });
    }
    // Arrow Functions assigned to variables
    else if (
      node.type === "VariableDeclarator" &&
      node.id?.type === "Identifier" &&
      node.init?.type === "ArrowFunctionExpression"
    ) {
      const typeInfo = extractTypeInfo(node.init);

      decls.push({
        name: node.id.name,
        type: "function",
        doc_comment: docComment,
        start_line: loc.start,
        end_line: loc.end,
        is_async: node.init.async || false,
        is_arrow: true,
        ...typeInfo,
      });
    }
    // Class Declarations
    else if (node.type === "ClassDeclaration" && node.id?.name) {
      const classInfo = {
        name: node.id.name,
        type: "class",
        doc_comment: docComment,
        start_line: loc.start,
        end_line: loc.end,
        is_abstract: node.abstract || false,
      };

      // Extract extends
      if (node.superClass) {
        if (node.superClass.type === "Identifier") {
          classInfo.extends = node.superClass.name;
        }
      }

      // Extract implements
      if (node.implements && node.implements.length > 0) {
        classInfo.implements = node.implements.map(
          (impl) => impl.expression.name || "unknown",
        );
      }

      // Extract generics
      if (node.typeParameters) {
        classInfo.generics = node.typeParameters.params.map(
          (tp) => tp.name.name,
        );
      }

      // Extract methods and properties
      const methods = [];
      const properties = [];

      if (node.body && node.body.body) {
        for (const member of node.body.body) {
          if (member.type === "MethodDefinition" && member.key?.name) {
            methods.push(member.key.name);
          } else if (member.type === "PropertyDefinition" && member.key?.name) {
            properties.push(member.key.name);
          }
        }
      }

      if (methods.length > 0) {
        classInfo.methods = methods;
      }
      if (properties.length > 0) {
        classInfo.properties = properties;
      }

      decls.push(classInfo);
    }
    // Interface Declarations
    else if (node.type === "TSInterfaceDeclaration" && node.id?.name) {
      const interfaceInfo = {
        name: node.id.name,
        type: "interface",
        doc_comment: docComment,
        start_line: loc.start,
        end_line: loc.end,
      };

      // Extract extends
      if (node.extends && node.extends.length > 0) {
        interfaceInfo.extends = node.extends.map(
          (ext) => ext.expression.name || "unknown",
        );
      }

      // Extract generics
      if (node.typeParameters) {
        interfaceInfo.generics = node.typeParameters.params.map(
          (tp) => tp.name.name,
        );
      }

      // Extract properties/methods
      const properties = [];
      const methods = [];

      if (node.body && node.body.body) {
        for (const member of node.body.body) {
          if (member.type === "TSPropertySignature" && member.key?.name) {
            properties.push(member.key.name);
          } else if (member.type === "TSMethodSignature" && member.key?.name) {
            methods.push(member.key.name);
          }
        }
      }

      if (properties.length > 0) {
        interfaceInfo.properties = properties;
      }
      if (methods.length > 0) {
        interfaceInfo.methods = methods;
      }

      decls.push(interfaceInfo);
    }
    // Type Alias Declarations
    else if (node.type === "TSTypeAliasDeclaration" && node.id?.name) {
      const typeInfo = {
        name: node.id.name,
        type: "type",
        doc_comment: docComment,
        start_line: loc.start,
        end_line: loc.end,
      };

      // Extract generics
      if (node.typeParameters) {
        typeInfo.generics = node.typeParameters.params.map(
          (tp) => tp.name.name,
        );
      }

      // Extract type definition
      if (node.typeAnnotation) {
        typeInfo.type_definition = extractTypeString(node.typeAnnotation);
      }

      decls.push(typeInfo);
    }
    // Enum Declarations
    else if (node.type === "TSEnumDeclaration" && node.id?.name) {
      const members = [];

      if (node.members) {
        for (const member of node.members) {
          if (member.id?.type === "Identifier") {
            members.push(member.id.name);
          }
        }
      }

      decls.push({
        name: node.id.name,
        type: "enum",
        doc_comment: docComment,
        start_line: loc.start,
        end_line: loc.end,
        members: members,
      });
    }
    // Variable Declarations (const, let, var)
    else if (
      node.type === "VariableDeclarator" &&
      node.id?.type === "Identifier"
    ) {
      const parent = parentMap.get(node);

      if (parent?.type === "VariableDeclaration") {
        const varInfo = {
          name: node.id.name,
          type: "variable",
          doc_comment: docComment,
          start_line: loc.start,
          end_line: loc.end,
          var_kind: parent.kind, // const, let, or var
        };

        // Extract type annotation
        if (node.id.typeAnnotation) {
          varInfo.var_type = extractTypeString(
            node.id.typeAnnotation.typeAnnotation,
          );
        }

        decls.push(varInfo);
      }
    }
    // Import Declarations
    else if (node.type === "ImportDeclaration" && node.source?.value) {
      const importInfo = {
        name: node.source.value,
        type: "import",
        start_line: loc.start,
        end_line: loc.end,
        import_path: node.source.value,
      };

      // Extract imported names
      const imports = [];
      if (node.specifiers) {
        for (const spec of node.specifiers) {
          if (spec.type === "ImportSpecifier" && spec.imported?.name) {
            imports.push(spec.imported.name);
          } else if (
            spec.type === "ImportDefaultSpecifier" &&
            spec.local?.name
          ) {
            imports.push(`default as ${spec.local.name}`);
          } else if (
            spec.type === "ImportNamespaceSpecifier" &&
            spec.local?.name
          ) {
            imports.push(`* as ${spec.local.name}`);
          }
        }
      }

      if (imports.length > 0) {
        importInfo.imported_names = imports;
      }

      decls.push(importInfo);
    }
    // Export Declarations
    else if (node.type === "ExportNamedDeclaration") {
      if (node.declaration) {
        // Export with declaration: export const x = ...
        visit(node.declaration);
      } else if (node.specifiers && node.specifiers.length > 0) {
        // Named exports: export { x, y }
        for (const spec of node.specifiers) {
          if (spec.exported?.name) {
            decls.push({
              name: spec.exported.name,
              type: "export",
              start_line: loc.start,
              end_line: loc.end,
              export_kind: "named",
            });
          }
        }
      }
    }

    // Continue traversing
    Object.values(node).forEach(visit);
  }

  visit(ast);
  return decls;
}

// Main execution
if (process.argv.length < 3) {
  console.error("Usage: node parse_ts.js <file.ts|file.js>");
  process.exit(1);
}

const filePath = path.resolve(process.argv[2]);

try {
  const code = fs.readFileSync(filePath, "utf8");
  const ast = parse(code, {
    sourceType: "module",
    loc: true,
    range: true,
    comment: false,
    ecmaFeatures: {
      jsx: true,
    },
  });

  const decls = extractDeclarations(ast, code);
  console.log(JSON.stringify(decls, null, 2));
} catch (e) {
  console.error(`ERR: ${e.message}`);
  process.exit(2);
}
