/**
 * Per-language symbol extractors for the tier-1 languages.
 *
 * Each tier-1 language has a `LanguageExtractor` providing tree-sitter query
 * strings. The `emit.ts` module compiles these against the loaded grammar,
 * runs them against the root node, and walks captures into FileIR.
 *
 * Query capture conventions (fixed across all languages):
 *
 *   Definitions — every definition pattern captures:
 *     @def.<kind>  the definition node (span = [startIndex, endIndex))
 *     @name        the identifier node whose .text is the symbol name
 *     <kind> ∈ function | method | class | interface | enum | type | module
 *
 *   Imports — every import pattern captures:
 *     @__import.stmt  the import-statement node (grouping key)
 *     @import.module  the module specifier text (cleaned in emit.ts)
 *     @import.name    zero or more imported-name identifiers
 *
 *   Exports — @export.name
 *   Call sites — @call.callee
 *   Routes — @route.verb + @route.path + @route.handler
 */
import type { CodingGraphLanguage } from "@remnic/core";

export type DefKind = "function" | "method" | "class" | "interface" | "enum" | "type" | "module";

export interface LanguageExtractor {
  readonly definitionsQuery: string;
  readonly importsQuery: string;
  readonly exportsQuery: string;
  readonly callSitesQuery: string;
  readonly routesQuery: string;
}

// ===========================================================================
// JS family (JavaScript, TypeScript, TSX) — shared definition + call patterns.
// ===========================================================================

const JS_FAMILY_DEFINITIONS = `
(function_declaration name: (identifier) @name) @def.function
(method_definition name: (property_identifier) @name) @def.method
(class_declaration name: (type_identifier) @name) @def.class
(interface_declaration name: (type_identifier) @name) @def.interface
(enum_declaration name: (identifier) @name) @def.enum
(type_alias_declaration name: (type_identifier) @name) @def.type

; const handler = () => {} / const handler = function () {}
; Indexes arrow-function and function-expression declarations so route
; handlers and React components defined this way appear as symbols.
(variable_declarator
  name: (identifier) @name
  value: [(arrow_function) (function_expression)]) @def.function
`.trim();

const JS_IMPORTS = `
(import_statement
  source: (string (string_fragment) @import.module)) @__import.stmt
; Default and namespace imports bind LOCAL aliases whose exported symbol
; is unknowable to Phase A (issue #1894 round 12): they appear in
; importedNames (informational) but never in bindings (call-bindable).
(import_statement
  (import_clause (identifier) @import.unboundName)
  source: (string (string_fragment) @import.module)) @__import.stmt
(import_statement
  (import_clause (namespace_import (identifier) @import.unboundName))
  source: (string (string_fragment) @import.module)) @__import.stmt
(import_statement
  (import_clause (named_imports (import_specifier name: (identifier) @import.name)))
  source: (string (string_fragment) @import.module)) @__import.stmt
; Aliased named import (issue #1894 review): import { foo as bar } — the
; local binding is the alias; the exported name stays the binding target.
(import_statement
  (import_clause (named_imports (import_specifier
    name: (identifier) @import.aliasExported
    alias: (identifier) @import.aliasLocal)))
  source: (string (string_fragment) @import.module)) @__import.stmt

; CommonJS require("...") — capture the module specifier so dependency
; edges exist for Node/CommonJS codebases, not just ES-module imports.
(call_expression
  function: (identifier) @__import.require
  arguments: (arguments (string (string_fragment) @import.module))
  (#eq? @__import.require "require")) @__import.stmt
`.trim();

// TS/TSX exports — class/interface names use type_identifier.
const TS_EXPORTS = `
(export_statement declaration: (function_declaration name: (identifier) @export.name))
(export_statement declaration: (class_declaration name: (type_identifier) @export.name))
(export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))
(export_statement declaration: (enum_declaration name: (identifier) @export.name))
(export_statement declaration: (interface_declaration name: (type_identifier) @export.name))
(export_statement (export_clause (export_specifier name: (identifier) @export.name)))

; export default App; (bare identifier re-export)
(export_statement (identifier) @export.name)
`.trim();

// JavaScript exports — class names use identifier (no type_identifier in JS grammar).
// Includes CommonJS export patterns (module.exports / exports.X) so
// Node/CommonJS public APIs are not marked unexported.
const JS_EXPORTS = `
(export_statement declaration: (function_declaration name: (identifier) @export.name))
(export_statement declaration: (class_declaration name: (identifier) @export.name))
(export_statement (lexical_declaration (variable_declarator name: (identifier) @export.name)))
(export_statement (export_clause (export_specifier name: (identifier) @export.name)))

; export default App; (bare identifier re-export)
(export_statement (identifier) @export.name)

; CommonJS: module.exports = { App, createRouter }
(assignment_expression
  left: (member_expression object: (identifier) @__cjs.mod property: (property_identifier) @__cjs.exp)
  right: (object (shorthand_property_identifier) @export.name)
  (#eq? @__cjs.mod "module") (#eq? @__cjs.exp "exports"))
; CommonJS: module.exports = { publicName: createRouter }
; Capture the VALUE identifier (the actual symbol), not the key (public alias).
(assignment_expression
  left: (member_expression object: (identifier) @__cjs.mod2 property: (property_identifier) @__cjs.exp2)
  right: (object (pair key: (property_identifier) @__cjs.key2 value: (identifier) @export.name))
  (#eq? @__cjs.mod2 "module") (#eq? @__cjs.exp2 "exports"))
; Fallback: { foo: <non-identifier-value> } — capture the key as the public name
; when the value is not a named symbol (arrow fn, literal, etc).
(assignment_expression
  left: (member_expression object: (identifier) @__cjs.mod2b property: (property_identifier) @__cjs.exp2b)
  right: (object (pair key: (property_identifier) @export.name value: (_) @__cjs.nonId))
  (#eq? @__cjs.mod2b "module") (#eq? @__cjs.exp2b "exports")
  (#not-match? @__cjs.nonId "^[A-Za-z_$][A-Za-z0-9_$]*$"))
; CommonJS: module.exports = App
(assignment_expression
  left: (member_expression object: (identifier) @__cjs.mod3 property: (property_identifier) @__cjs.exp3)
  right: (identifier) @export.name
  (#eq? @__cjs.mod3 "module") (#eq? @__cjs.exp3 "exports"))
; CommonJS: exports.handler = handler
(assignment_expression
  left: (member_expression object: (identifier) @__cjs.exp4 property: (property_identifier) @export.name)
  (#eq? @__cjs.exp4 "exports"))
`.trim();

const JS_CALLS = `
(call_expression function: (identifier) @call.callee)
; Member calls are captured separately (issue #1894 review): obj.save()'s
; \`save\` must never bind a bare visible symbol — method dispatch is
; Phase B (LSP) territory. The emit layer marks these memberAccess: true.
(call_expression function: (member_expression property: (property_identifier) @call.member))
`.trim();

const JS_ROUTES = `
; Unified route pattern: captures verb + path + the arguments node.
; emit.ts extracts the handler from the LAST argument (identifier,
; arrow_function, or function_expression), correctly handling middleware:
;   app.get("/users", requireAuth, getUsers) → handler=getUsers
(call_expression
  function: (member_expression
    object: (_) @__route.app
    property: (property_identifier) @route.verb)
  arguments: (arguments
    . (string (string_fragment) @route.path)
  ) @route.args
  (#match? @route.verb "^(get|post|put|patch|delete|head|options|all|use)$"))
`.trim();

// ===========================================================================
// Per-language extractors.
// ===========================================================================

const TYPESCRIPT_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: JS_FAMILY_DEFINITIONS,
  importsQuery: JS_IMPORTS,
  exportsQuery: TS_EXPORTS,
  callSitesQuery: JS_CALLS,
  routesQuery: JS_ROUTES,
};

const TSX_EXTRACTOR: LanguageExtractor = TYPESCRIPT_EXTRACTOR;

const JAVASCRIPT_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_declaration name: (identifier) @name) @def.function
(method_definition name: (property_identifier) @name) @def.method
(class_declaration name: (identifier) @name) @def.class

; const handler = () => {} / const handler = function () {}
(variable_declarator
  name: (identifier) @name
  value: [(arrow_function) (function_expression)]) @def.function
`.trim(),
  importsQuery: JS_IMPORTS,
  exportsQuery: JS_EXPORTS,
  callSitesQuery: JS_CALLS,
  routesQuery: JS_ROUTES,
};

const PYTHON_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_definition name: (identifier) @name) @def.function
(class_definition name: (identifier) @name) @def.class
`.trim(),
  importsQuery: `
(import_statement (dotted_name) @import.module) @__import.stmt
(import_from_statement module_name: (dotted_name) @import.module) @__import.stmt
; Imported names (issue #1894 review round 7): without these captures,
; extractImports emits importedNames: [] and the heuristic resolver's
; import bindings never fire for real Python parses.
(import_from_statement
  module_name: (dotted_name) @import.module
  name: (dotted_name) @import.name) @__import.stmt
(import_from_statement
  module_name: (dotted_name) @import.module
  name: (aliased_import
    name: (dotted_name) @import.aliasExported
    alias: (identifier) @import.aliasLocal)) @__import.stmt
; Python relative imports: from .models import User / from ..parent import X
; tree-sitter-python wraps the module inside a relative_import node, so the
; module_name field is NOT set. Capture the relative_import node itself so
; the prefix dots are preserved (..parent, not just parent) — different
; relative levels must not collapse to the same module name
; (chatgpt-codex-connector #1688 P2: 'Preserve dots in Python relative imports').
(import_from_statement (relative_import) @import.module) @__import.stmt
(import_from_statement
  (relative_import) @import.module
  name: (dotted_name) @import.name) @__import.stmt
(import_from_statement
  (relative_import) @import.module
  name: (aliased_import
    name: (dotted_name) @import.aliasExported
    alias: (identifier) @import.aliasLocal)) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call function: (identifier) @call.callee)
(call function: (attribute attribute: (identifier) @call.member))
`.trim(),
  routesQuery: `
(decorated_definition
  (decorator
    (call function: (attribute attribute: (identifier) @route.verb)
      arguments: (argument_list (string) @route.path)))
  definition: (function_definition name: (identifier) @route.handler)
  (#match? @route.verb "^(get|post|put|patch|delete|route|api_route)$"))
`.trim(),
};

const GO_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_declaration name: (identifier) @name) @def.function

; Go methods sit outside their receiver struct, so byte-span nesting
; cannot compute qualified names. Capture the receiver type_identifier
; so extractSymbols can prefix the method name (Server.Start).
(method_declaration
  receiver: (parameter_list
    (parameter_declaration type: (type_identifier) @__receiver.type))
  name: (field_identifier) @name) @def.method
(method_declaration
  receiver: (parameter_list
    (parameter_declaration type: (pointer_type (type_identifier) @__receiver.type)))
  name: (field_identifier) @name) @def.method
(type_spec name: (type_identifier) @name type: (struct_type)) @def.class
(type_spec name: (type_identifier) @name type: (interface_type)) @def.interface
(type_spec name: (type_identifier) @name type: (type_identifier)) @def.type
`.trim(),
  importsQuery: `
(import_spec path: (interpreted_string_literal) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call_expression function: (identifier) @call.callee)
(call_expression function: (selector_expression field: (field_identifier) @call.member))
`.trim(),
  routesQuery: ``,
};

const RUST_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_item name: (identifier) @name) @def.function
(function_signature_item name: (identifier) @name) @def.function
(struct_item name: (type_identifier) @name) @def.class
(enum_item name: (type_identifier) @name) @def.enum
(trait_item name: (type_identifier) @name) @def.interface
(type_item name: (type_identifier) @name) @def.type
(mod_item name: (identifier) @name) @def.module

; Rust impl methods — the impl block sits outside the struct's byte span,
; so byte-span nesting cannot compute the parent struct. Capture the impl
; type_identifier so extractSymbols can prefix qualified names (Config.new).
; These also match functions caught by the general patterns above;
; extractSymbols deduplicates by node identity (startByte+endByte+name).
(impl_item
  type: (type_identifier) @__receiver.type
  body: (declaration_list
    (function_item name: (identifier) @name) @def.method))
(impl_item
  type: (type_identifier) @__receiver.type
  body: (declaration_list
    (function_signature_item name: (identifier) @name) @def.method))
`.trim(),
  importsQuery: `
(use_declaration (scoped_identifier) @import.module) @__import.stmt
(use_declaration (scoped_use_list) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call_expression function: (identifier) @call.callee)
(call_expression function: (field_expression field: (field_identifier) @call.member))
(call_expression function: (scoped_identifier) @call.callee)
`.trim(),
  routesQuery: ``,
};

const JAVA_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(class_declaration name: (identifier) @name) @def.class
(interface_declaration name: (identifier) @name) @def.interface
(enum_declaration name: (identifier) @name) @def.enum
(record_declaration name: (identifier) @name) @def.class
(method_declaration name: (identifier) @name) @def.method
(constructor_declaration name: (identifier) @name) @def.method
`.trim(),
  importsQuery: `
(import_declaration (scoped_identifier) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(method_invocation !object name: (identifier) @call.callee)
(method_invocation object: (_) name: (identifier) @call.member)
`.trim(),
  routesQuery: ``,
};

const C_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function
(type_definition declarator: (type_identifier) @name) @def.type
`.trim(),
  importsQuery: `
(preproc_include path: (system_lib_string) @import.module) @__import.stmt
(preproc_include path: (string_literal) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call_expression function: (identifier) @call.callee)
`.trim(),
  routesQuery: ``,
};

const CPP_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @def.function
(function_definition declarator: (function_declarator declarator: (field_identifier) @name)) @def.method
; C++ out-of-class method definitions: void A::start() {}
; The qualified_identifier (A::start) encodes the full qualified name.
(function_definition declarator: (function_declarator declarator: (qualified_identifier) @name)) @def.method
(class_specifier name: (type_identifier) @name) @def.class
(struct_specifier name: (type_identifier) @name) @def.class
(enum_specifier name: (type_identifier) @name) @def.enum
(namespace_definition name: (namespace_identifier) @name) @def.module
`.trim(),
  importsQuery: `
(preproc_include path: (system_lib_string) @import.module) @__import.stmt
(preproc_include path: (string_literal) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call_expression function: (identifier) @call.callee)
(call_expression function: (field_expression field: (field_identifier) @call.member))
`.trim(),
  routesQuery: ``,
};

const CSHARP_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(class_declaration name: (identifier) @name) @def.class
(interface_declaration name: (identifier) @name) @def.interface
(enum_declaration name: (identifier) @name) @def.enum
(struct_declaration name: (identifier) @name) @def.class
(method_declaration name: (identifier) @name) @def.method
`.trim(),
  importsQuery: `
(using_directive (identifier) @import.module) @__import.stmt
(using_directive (qualified_name) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(invocation_expression function: (identifier) @call.callee)
(invocation_expression function: (member_access_expression name: (identifier) @call.member))
`.trim(),
  routesQuery: ``,
};

const RUBY_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(class name: (constant) @name) @def.class
(module name: (constant) @name) @def.module
(method name: (identifier) @name) @def.method
(singleton_method name: (identifier) @name) @def.method
`.trim(),
  importsQuery: `
(call
  method: (identifier) @__import.method
  arguments: (argument_list (string) @import.module)
  (#match? @__import.method "^(require|require_relative|load)$")) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call !receiver method: (identifier) @call.callee)
(call receiver: (_) method: (identifier) @call.member)
`.trim(),
  routesQuery: ``,
};

const PHP_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(class_declaration name: (name) @name) @def.class
(interface_declaration name: (name) @name) @def.interface
(trait_declaration name: (name) @name) @def.class
(function_definition name: (name) @name) @def.function
(method_declaration name: (name) @name) @def.method
`.trim(),
  importsQuery: `
(namespace_use_declaration (namespace_use_clause (qualified_name) @import.module)) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(function_call_expression function: (name) @call.callee)
(member_call_expression (name) @call.member)
`.trim(),
  routesQuery: ``,
};

const KOTLIN_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(class_declaration (type_identifier) @name) @def.class
(object_declaration (type_identifier) @name) @def.module
(function_declaration (simple_identifier) @name) @def.function
`.trim(),
  importsQuery: `
; Capture the full identifier node — its .text is the complete import
; path (e.g. "kotlin.collections"). Do NOT capture nested simple_identifier
; children, which would emit bogus segment-level modules.
(import_header (identifier) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call_expression (simple_identifier) @call.callee)
`.trim(),
  routesQuery: ``,
};

const SWIFT_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(class_declaration name: (type_identifier) @name) @def.class
(protocol_declaration name: (type_identifier) @name) @def.interface
(function_declaration name: (simple_identifier) @name) @def.function
`.trim(),
  importsQuery: `
(import_declaration (identifier) @import.module) @__import.stmt
`.trim(),
  exportsQuery: ``,
  callSitesQuery: `
(call_expression (simple_identifier) @call.callee)
`.trim(),
  routesQuery: ``,
};

const BASH_EXTRACTOR: LanguageExtractor = {
  definitionsQuery: `
(function_definition name: (word) @name) @def.function
`.trim(),
  importsQuery: ``,
  exportsQuery: ``,
  callSitesQuery: `
(command name: (command_name (word) @call.callee))
`.trim(),
  routesQuery: ``,
};

// ===========================================================================
// Registry — single source of truth.
// ===========================================================================

export const EXTRACTORS: Record<CodingGraphLanguage, LanguageExtractor> = {
  typescript: TYPESCRIPT_EXTRACTOR,
  tsx: TSX_EXTRACTOR,
  javascript: JAVASCRIPT_EXTRACTOR,
  python: PYTHON_EXTRACTOR,
  go: GO_EXTRACTOR,
  rust: RUST_EXTRACTOR,
  java: JAVA_EXTRACTOR,
  c: C_EXTRACTOR,
  cpp: CPP_EXTRACTOR,
  csharp: CSHARP_EXTRACTOR,
  ruby: RUBY_EXTRACTOR,
  php: PHP_EXTRACTOR,
  kotlin: KOTLIN_EXTRACTOR,
  swift: SWIFT_EXTRACTOR,
  bash: BASH_EXTRACTOR,
};

const VALID_KINDS = new Set<string>([
  "function",
  "method",
  "class",
  "interface",
  "enum",
  "type",
  "module",
]);

/**
 * Extract the SymbolIR kind from a `@def.<kind>` capture name.
 * Returns `null` for non-definition captures or invalid kinds.
 */
export function kindFromCapture(captureName: string): DefKind | null {
  if (!captureName.startsWith("def.")) return null;
  const kind = captureName.slice(4);
  if (!VALID_KINDS.has(kind)) return null;
  return kind as DefKind;
}
