package forest

import (
	"fmt"
	"sort"
	"testing"
	"unsafe"

	adaforest "github.com/alexaandru/go-sitter-forest/ada"
	apexforest "github.com/alexaandru/go-sitter-forest/apex"
	bladeforest "github.com/alexaandru/go-sitter-forest/blade"
	clojureforest "github.com/alexaandru/go-sitter-forest/clojure"
	cmakeforest "github.com/alexaandru/go-sitter-forest/cmake"
	cobolforest "github.com/alexaandru/go-sitter-forest/cobol"
	crystalforest "github.com/alexaandru/go-sitter-forest/crystal"
	dforest "github.com/alexaandru/go-sitter-forest/d"
	elispforest "github.com/alexaandru/go-sitter-forest/elisp"
	erlangforest "github.com/alexaandru/go-sitter-forest/erlang"
	fortranforest "github.com/alexaandru/go-sitter-forest/fortran"
	fsharpforest "github.com/alexaandru/go-sitter-forest/fsharp"
	gdscriptforest "github.com/alexaandru/go-sitter-forest/gdscript"
	groovyforest "github.com/alexaandru/go-sitter-forest/groovy"
	hareforest "github.com/alexaandru/go-sitter-forest/hare"
	haskellforest "github.com/alexaandru/go-sitter-forest/haskell"
	jinjaforest "github.com/alexaandru/go-sitter-forest/jinja"
	juliaforest "github.com/alexaandru/go-sitter-forest/julia"
	liquidforest "github.com/alexaandru/go-sitter-forest/liquid"
	matlabforest "github.com/alexaandru/go-sitter-forest/matlab"
	moveforest "github.com/alexaandru/go-sitter-forest/move"
	nimforest "github.com/alexaandru/go-sitter-forest/nim"
	nixforest "github.com/alexaandru/go-sitter-forest/nix"
	alforest "github.com/alexaandru/go-sitter-forest/al"
	objcforest "github.com/alexaandru/go-sitter-forest/objc"
	odinforest "github.com/alexaandru/go-sitter-forest/odin"
	pascalforest "github.com/alexaandru/go-sitter-forest/pascal"
	perlforest "github.com/alexaandru/go-sitter-forest/perl"
	powershellforest "github.com/alexaandru/go-sitter-forest/powershell"
	pugforest "github.com/alexaandru/go-sitter-forest/pug"
	racketforest "github.com/alexaandru/go-sitter-forest/racket"
	rescriptforest "github.com/alexaandru/go-sitter-forest/rescript"
	solidityforest "github.com/alexaandru/go-sitter-forest/solidity"
	tactforest "github.com/alexaandru/go-sitter-forest/tact"
	tclforest "github.com/alexaandru/go-sitter-forest/tcl"
	twigforest "github.com/alexaandru/go-sitter-forest/twig"
	valaforest "github.com/alexaandru/go-sitter-forest/vala"
	vimforest "github.com/alexaandru/go-sitter-forest/vim"
	zigforest "github.com/alexaandru/go-sitter-forest/zig"

	"github.com/zzet/gortex/internal/parser"
	sitter "github.com/zzet/gortex/internal/parser/tsitter"
)

// TestDumpGrammarKinds is a research helper. It parses a small
// fixture per grammar and prints every named node kind seen, with
// frequencies — feeds the per-language node-kind dispatch table.
// Run with: go test -run TestDumpGrammarKinds -v ./internal/parser/forest/
func TestDumpGrammarKinds(t *testing.T) {
	cases := []struct {
		name string
		get  func() unsafe.Pointer
		src  string
	}{
		{"erlang", erlangforest.GetLanguage, `-module(m).
-export([add/2]).
add(A, B) -> A + B.
multiply(A, B) -> A * B.
`},
		{"haskell", haskellforest.GetLanguage, `module M where
import Data.List
data Color = Red | Green
type Name = String
type Point = (Int, Int)
newtype Wrap a = Wrap a
class Show a where
  show :: a -> String
instance Show Color where
  show Red = "red"
add :: Int -> Int -> Int
add x y = x + y
`},
		{"crystal", crystalforest.GetLanguage, `module Greeter
  class Hello
    def say
      "hi"
    end
  end
  struct Point
  end
end
`},
		{"nim", nimforest.GetLanguage, `import strutils
import sequtils, tables

type
  Point* = object
    x*, y*: int

  Shape = enum
    Circle, Square

proc distance*(a, b: Point): float =
  return 0.0

func double(n: int): int =
  n * 2
`},
		{"ada", adaforest.GetLanguage, `with Ada.Text_IO; use Ada.Text_IO;
package body M is
   procedure Hello is begin Put_Line ("hi"); end Hello;
   function Add (X, Y : Integer) return Integer is begin return X + Y; end Add;
end M;
`},
		{"fortran", fortranforest.GetLanguage, `module greetings
contains
  subroutine hello
    print *, 'hi'
  end subroutine hello
  function add(a, b)
    integer :: a, b, add
    add = a + b
  end function add
end module greetings
`},
		{"vim", vimforest.GetLanguage, `function! Hello() abort
  echo 'hi'
endfunction
function! s:add(a, b)
  return a:a + a:b
endfunction
`},
		{"tcl", tclforest.GetLanguage, `proc hello {} { puts hi }
proc add {a b} { return [expr {$a + $b}] }
package require Tcl 8.6
`},
		{"perl", perlforest.GetLanguage, `package M;
use strict;
sub hello { print "hi\n" }
sub add { my ($a,$b) = @_; return $a+$b; }
1;
`},
		{"powershell", powershellforest.GetLanguage, `function Hello { Write-Host "hi" }
function Add($a, $b) { return $a + $b }
class Greeter { [string]$Name }
`},
		{"pascal", pascalforest.GetLanguage, `program M;
type Point = record x, y: integer; end;
procedure Hello; begin writeln('hi'); end;
function Add(a, b: integer): integer; begin Add := a + b; end;
begin Hello; end.
`},
		{"odin", odinforest.GetLanguage, `package m
import "core:fmt"
Point :: struct { x, y: int }
hello :: proc() { fmt.println("hi") }
add :: proc(a, b: int) -> int { return a + b }
`},
		{"hare", hareforest.GetLanguage, `use io;
type point = struct { x: int, y: int };
fn add(a: int, b: int) int = a + b;
export fn hello() void = io::println("hi");
`},
		{"zig", zigforest.GetLanguage, `const std = @import("std");
const Point = struct { x: i32, y: i32 };
fn add(a: i32, b: i32) i32 { return a + b; }
pub fn hello() void {}
`},
		{"d", dforest.GetLanguage, `module m;
import std.stdio;
struct Point { int x, y; }
class Greeter { void hello() {} }
int add(int a, int b) { return a + b; }
`},
		{"vala", valaforest.GetLanguage, `using GLib;
namespace M { class Greeter : Object { public void hello() {} } }
`},
		{"groovy", groovyforest.GetLanguage, `package m
class Greeter { String hello() { return 'hi' } }
def add(a, b) { return a + b }
`},
		{"clojure", clojureforest.GetLanguage, `(ns m (:require [clojure.string :as s]))
(defn hello [] (println "hi"))
(defn add [a b] (+ a b))
(defrecord Point [x y])
`},
		{"cmake", cmakeforest.GetLanguage, `cmake_minimum_required(VERSION 3.10)
function(hello)
  message("hi")
endfunction()
macro(double x)
  set(${x}_2 ${${x}}*2)
endmacro()
`},
		{"cobol", cobolforest.GetLanguage, `IDENTIFICATION DIVISION.
       PROGRAM-ID. M.
       PROCEDURE DIVISION.
           DISPLAY 'hi'.
           STOP RUN.
`},
		{"julia", juliaforest.GetLanguage, `module M
using LinearAlgebra
struct Point x::Int; y::Int end
function hello() println("hi") end
add(a, b) = a + b
end
`},
		{"matlab", matlabforest.GetLanguage, `function out = add(a, b)
  out = a + b;
end
classdef Greeter
  methods
    function hello(obj); disp('hi'); end
  end
end
`},
		{"apex", apexforest.GetLanguage, `public class Greeter {
  public String hello() { return 'hi'; }
  public Integer add(Integer a, Integer b) { return a + b; }
}
trigger AccountTrigger on Account (before insert) {}
`},
		{"solidity", solidityforest.GetLanguage, `pragma solidity ^0.8.0;
import "./X.sol";
interface IFoo { function bar() external; }
contract Token { function mint(address to, uint256 amt) public {} event Tx(); modifier ok() { _; } struct Holder {} enum State {} }
`},
		{"tact", tactforest.GetLanguage, `import "./other";
trait T { fun base() {} }
contract Greeter with T {
  init() { }
  fun hello(): String { return "hi"; }
  receive("ping") {}
}
`},
		{"move", moveforest.GetLanguage, `module 0x1::M {
  use std::vector;
  struct Counter has key { value: u64 }
  public fun increment(c: &mut Counter) { c.value = c.value + 1; }
}
`},
		{"racket", racketforest.GetLanguage, `#lang racket
(require racket/string)
(define (hello) (displayln "hi"))
(define (add a b) (+ a b))
(struct point (x y))
`},
		{"elisp", elispforest.GetLanguage, `(require 'cl-lib)
(defun hello () "hi")
(defun add (a b) (+ a b))
(defvar greeting "hi")
(defmacro when-bind (b &rest body) (list 'let b body))
`},
		{"fsharp", fsharpforest.GetLanguage, `module M
open System
type Point = { x: int; y: int }
let hello () = printfn "hi"
let add a b = a + b
`},
		{"gdscript", gdscriptforest.GetLanguage, `extends Node
class_name Greeter
signal greeted(name)
func hello():
    print("hi")
func add(a: int, b: int) -> int:
    return a + b
`},
		{"jinja", jinjaforest.GetLanguage, `{% extends 'base.html' %}
{% block content %}
  {% macro greet(name) %}Hi, {{ name }}{% endmacro %}
  {{ greet('world') }}
{% endblock %}
`},
		{"liquid", liquidforest.GetLanguage, `{% include 'header' %}
{% capture greeting %}Hi{% endcapture %}
{% assign name = "world" %}
{{ greeting }}, {{ name }}
`},
		{"twig", twigforest.GetLanguage, `{% extends 'base.twig' %}
{% block content %}
  {% macro greet(name) %}Hi, {{ name }}{% endmacro %}
{% endblock %}
`},
		{"pug", pugforest.GetLanguage, `extends layout
block content
  mixin greet(name)
    p Hi #{name}
  +greet('world')
`},
		{"blade", bladeforest.GetLanguage, `@extends('layouts.app')
@section('content')
  @include('partials.header')
  <p>Hi {{ $name }}</p>
@endsection
`},
		{"rescript", rescriptforest.GetLanguage, `module M = {
  type point = { x: int, y: int }
  let hello = () => Js.log("hi")
  let add = (a, b) => a + b
}
`},
		{"nix", nixforest.GetLanguage, `{ lib, ... }:
let
  greet = name: "Hi, ${name}";
  add = a: b: a + b;
in {
  inherit greet add;
}
`},
		{"objc", objcforest.GetLanguage, `#import <Foundation/Foundation.h>
@interface Greeter : NSObject
- (NSString *)hello;
@end
@implementation Greeter
- (NSString *)hello { return @"hi"; }
@end
`},
		{"al", alforest.GetLanguage, `table 50000 Customer
{
    fields { field(1; Name; Text[100]) {} }
}
codeunit 50001 CustomerMgt
{
    procedure Hello() begin Message('hi'); end;
}
page 50002 CustList { SourceTable = Customer; }
`},
	}

	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			lang := sitter.NewLanguage(c.get())
			tree, err := parser.ParseFile([]byte(c.src), lang)
			if err != nil {
				t.Fatalf("parse: %v", err)
			}
			defer tree.Close()

			kinds := make(map[string]int)
			walkKinds(tree.RootNode(), kinds)

			keys := make([]string, 0, len(kinds))
			for k := range kinds {
				keys = append(keys, k)
			}
			sort.Strings(keys)

			fmt.Printf("=== %s ===\n", c.name)
			for _, k := range keys {
				fmt.Printf("  %-40s × %d\n", k, kinds[k])
			}
		})
	}
}

func walkKinds(n *sitter.Node, kinds map[string]int) {
	if n == nil {
		return
	}
	if n.IsNamed() {
		kinds[n.Type()]++
	}
	for i := 0; i < int(n.NamedChildCount()); i++ {
		walkKinds(n.NamedChild(i), kinds)
	}
}
