pkg_check_modules(CMARK_GFM REQUIRED IMPORTED_TARGET libcmark-gfm)
pkg_check_modules(TREE_SITTER REQUIRED IMPORTED_TARGET tree-sitter)
find_path(JKQTPlotter6_CMAKE_DIR
	NAMES JKQTMathText6Config.cmake
	HINTS /usr/lib/cmake/JKQTPlotter6 /usr/local/lib/cmake/JKQTPlotter6
	DOC "Directory containing the JKQtPlotter cmake package files")
find_package(JKQTMathText6 REQUIRED PATHS "${JKQTPlotter6_CMAKE_DIR}")

function(_ts_entry_point out file)
	get_filename_component(_ts_nm_base "${file}" NAME)
	set(_ts_nm_file "${CMAKE_CURRENT_BINARY_DIR}/ts-entry-${_ts_nm_base}")
	execute_process(
		COMMAND nm -D --defined-only "${file}"
		RESULT_VARIABLE _ts_nm_rc
		OUTPUT_FILE "${_ts_nm_file}"
		ERROR_FILE "${_ts_nm_file}.err")
	set(_ts_sym "tree_sitter_missing")
	if(_ts_nm_rc EQUAL 0 AND EXISTS "${_ts_nm_file}")
		file(READ "${_ts_nm_file}" _ts_nm_out)
		file(REMOVE "${_ts_nm_file}" "${_ts_nm_file}.err")
		string(REGEX MATCHALL " T tree_sitter_[A-Za-z0-9_]+" _ts_syms "${_ts_nm_out}")
		foreach(_ts_s IN LISTS _ts_syms)
			string(REPLACE " T " "" _ts_s "${_ts_s}")
			if(NOT _ts_s MATCHES "_external")
				set(_ts_sym "${_ts_s}")
				break()
			endif()
		endforeach()
	endif()
	set(${out} "${_ts_sym}" PARENT_SCOPE)
endfunction()

function(_ts_expand_inherits query_dir text outVar)
	set(result "${text}")
	set(_visited "")
	set(_depth 0)
	while(_depth LESS 8)
		string(REGEX MATCHALL "inherits:[ \t]*[A-Za-z0-9_]+" _inh "${result}")
		if(NOT _inh)
			break()
		endif()
		set(_added FALSE)
		foreach(_entry IN LISTS _inh)
			string(REGEX REPLACE "^inherits:[ \t]*" "" _name "${_entry}")
			set(_file "${query_dir}/${_name}/highlights.scm")
			if(NOT EXISTS "${_file}" OR _file IN_LIST _visited)
				continue()
			endif()
			list(APPEND _visited "${_file}")
			file(READ "${_file}" _inh_text)
			string(APPEND result "\n${_inh_text}")
			set(_added TRUE)
		endforeach()
		if(NOT _added)
			break()
		endif()
		math(EXPR _depth "${_depth} + 1")
	endwhile()
	set(${outVar} "${result}" PARENT_SCOPE)
endfunction()

function(_ts_add_query sid text filesVar hashesVar)
	set(files "${${filesVar}}")
	set(hashes "${${hashesVar}}")
	string(SHA256 _ts_qh "${text}")
	if(_ts_qh IN_LIST hashes)
		return()
	endif()
	list(APPEND hashes "${_ts_qh}")
	list(LENGTH files _ts_qi)
	set(_ts_qfile "${CMAKE_CURRENT_BINARY_DIR}/ts-queries/${sid}_${_ts_qi}.scm")
	file(WRITE "${_ts_qfile}" "${text}")
	list(APPEND files "${_ts_qfile}")
	set(${filesVar} "${files}" PARENT_SCOPE)
	set(${hashesVar} "${hashes}" PARENT_SCOPE)
endfunction()

set(_ts_candidates "") # entries: <id>|<cmake-safe id>|<lib>|<symbol>
file(GLOB _ts_sys_files
	"/usr/lib/libtree-sitter-*.so" "/usr/local/lib/libtree-sitter-*.so")
foreach(_ts_file IN LISTS _ts_sys_files)
	get_filename_component(_ts_name "${_ts_file}" NAME)
	string(REGEX REPLACE "^libtree-sitter-(.+)\.so$" "\\1" _ts_id "${_ts_name}")
	string(REPLACE "-" "_" _ts_sid "${_ts_id}")
	_ts_entry_point(_ts_sym "${_ts_file}")
	list(APPEND _ts_candidates
		"${_ts_id}|${_ts_sid}|libtree-sitter-${_ts_id}.so|${_ts_sym}")
endforeach()
set(_ts_nvim_parser_dirs
	"$ENV{HOME}/.local/share/nvim/site/parser"
	"$ENV{HOME}/.local/share/nvim/runtime/parser"
	"$ENV{HOME}/.local/share/nvim/lazy/nvim-treesitter/parser")
set(_ts_nvim_query_dirs
	"$ENV{HOME}/.local/share/nvim/site/queries"
	"$ENV{HOME}/.local/share/nvim/lazy/nvim-treesitter/runtime/queries")
foreach(_ts_dir IN LISTS _ts_nvim_parser_dirs)
	file(GLOB _ts_dir_files "${_ts_dir}/*.so")
	foreach(_ts_file IN LISTS _ts_dir_files)
		get_filename_component(_ts_name "${_ts_file}" NAME)
		string(REGEX REPLACE "\\.so$" "" _ts_id "${_ts_name}")
		string(REPLACE "-" "_" _ts_sid "${_ts_id}")
		_ts_entry_point(_ts_sym "${_ts_file}")
		list(APPEND _ts_candidates
			"${_ts_id}|${_ts_sid}|${_ts_file}|${_ts_sym}")
	endforeach()
endforeach()

set(_ts_ids "")
foreach(_ts_c IN LISTS _ts_candidates)
	string(REPLACE "|" ";" _ts_parts "${_ts_c}")
	list(GET _ts_parts 0 _ts_id)
	if(NOT _ts_id IN_LIST _ts_ids)
		list(APPEND _ts_ids "${_ts_id}")
	endif()
endforeach()
list(SORT _ts_ids)

set(HIGHLIGHT_QUERIES_HPP "${CMAKE_CURRENT_BINARY_DIR}/highlight-queries.hpp")
set(_hl_header
"#pragma once\n\n// Generated by CMake. Discoverd tree-sitter grammars and their\n// highlight queries: vendored highlight-queries/*.scm (MIT), Neovim\n// nvim-treesitter queries, and tree-sitter/highlighting (MIT).\nnamespace ZShell::llm::hq {\nstruct Candidate { const char* lib; const char* symbol; };\nstruct Grammar { const char* id; int nCandidates; const Candidate* candidates; int nQueries; const char* const* queries; };\n")
set(_ts_grammar_rows "")
set(_ts_vendored
	c cpp python javascript typescript tsx bash json rust go yaml toml sql)
foreach(_ts_id IN LISTS _ts_ids)
	string(REPLACE "-" "_" _ts_sid "${_ts_id}")

	set(_ts_qfiles "")
	set(_ts_qhashes "")
	if(_ts_id STREQUAL "cpp")
		file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/c.scm" _ts_qa)
		file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/cpp.scm" _ts_qb)
		_ts_add_query(${_ts_sid} "${_ts_qa}\n${_ts_qb}" _ts_qfiles _ts_qhashes)
	elseif(_ts_id STREQUAL "typescript" OR _ts_id STREQUAL "tsx")
		file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/javascript.scm" _ts_qa)
		file(READ "${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/typescript.scm" _ts_qb)
		_ts_add_query(${_ts_sid} "${_ts_qa}\n${_ts_qb}" _ts_qfiles _ts_qhashes)
		_ts_add_query(${_ts_sid} "${_ts_qb}" _ts_qfiles _ts_qhashes)
	elseif(_ts_id IN_LIST _ts_vendored)
		file(READ
			"${CMAKE_CURRENT_SOURCE_DIR}/highlight-queries/${_ts_id}.scm" _ts_q)
		_ts_add_query(${_ts_sid} "${_ts_q}" _ts_qfiles _ts_qhashes)
	endif()
	foreach(_ts_qd IN LISTS _ts_nvim_query_dirs)
		if(EXISTS "${_ts_qd}/${_ts_id}/highlights.scm")
			file(READ "${_ts_qd}/${_ts_id}/highlights.scm" _ts_q)
			_ts_expand_inherits("${_ts_qd}" "${_ts_q}" _ts_q)
			_ts_add_query(${_ts_sid} "${_ts_q}" _ts_qfiles _ts_qhashes)
		endif()
	endforeach()
	if(NOT _ts_qfiles)
		set(_ts_dl "${CMAKE_CURRENT_BINARY_DIR}/ts-query-downloads/${_ts_sid}.scm")
		if(NOT EXISTS "${_ts_dl}")
			file(DOWNLOAD
				"https://raw.githubusercontent.com/tree-sitter/highlighting/main/queries/${_ts_id}/highlight.scm"
				"${_ts_dl}" STATUS _ts_dl_status TIMEOUT 30)
			list(GET _ts_dl_status 0 _ts_dl_rc)
			if(NOT _ts_dl_rc EQUAL 0)
				file(REMOVE "${_ts_dl}")
			endif()
		endif()
		if(EXISTS "${_ts_dl}")
			file(READ "${_ts_dl}" _ts_q)
			_ts_add_query(${_ts_sid} "${_ts_q}" _ts_qfiles _ts_qhashes)
		endif()
	endif()
	list(LENGTH _ts_qfiles _ts_nq)
	if(_ts_nq EQUAL 0)
		continue()
	endif()

	set(_ts_qn 0)
	set(_ts_q_ptrs "")
	foreach(_ts_qfile IN LISTS _ts_qfiles)
		file(READ "${_ts_qfile}" _ts_q)
		string(APPEND _hl_header
			"inline constexpr const char* q_${_ts_sid}_${_ts_qn} = R\"ZSQUERY(${_ts_q})ZSQUERY\";\n")
		string(APPEND _ts_q_ptrs "q_${_ts_sid}_${_ts_qn}, ")
		math(EXPR _ts_qn "${_ts_qn} + 1")
	endforeach()
	string(APPEND _hl_header
		"inline constexpr const char* const q_${_ts_sid}[] = { ${_ts_q_ptrs} };\n")

	string(APPEND _hl_header "inline constexpr Candidate cand_${_ts_sid}[] = {\n")
	set(_ts_nc 0)
	foreach(_ts_c IN LISTS _ts_candidates)
		string(REPLACE "|" ";" _ts_parts "${_ts_c}")
		list(GET _ts_parts 0 _ts_cid)
		if(_ts_cid STREQUAL _ts_id)
			list(GET _ts_parts 2 _ts_lib)
			list(GET _ts_parts 3 _ts_sym)
			string(APPEND _hl_header
				"    { R\"ZSLIB(${_ts_lib})ZSLIB\", R\"ZSSYM(${_ts_sym})ZSSYM\" },\n")
			math(EXPR _ts_nc "${_ts_nc} + 1")
		endif()
	endforeach()
	string(APPEND _hl_header "};\n")
	if(_ts_nc EQUAL 0)
		string(APPEND _hl_header "") # (arrays stay; grammar row is skipped)
		continue()
	endif()

	list(APPEND _ts_grammar_rows
		"{ \"${_ts_id}\", ${_ts_nc}, cand_${_ts_sid}, ${_ts_nq}, q_${_ts_sid} },")
endforeach()
string(APPEND _hl_header "inline constexpr Grammar grammars[] = {\n")
foreach(_ts_row IN LISTS _ts_grammar_rows)
	string(APPEND _hl_header "    ${_ts_row}\n")
endforeach()
string(APPEND _hl_header "};\n}\n")
file(WRITE "${HIGHLIGHT_QUERIES_HPP}" "${_hl_header}")

set(LM_FONTS
	lmroman10-regular
	lmroman10-italic
	lmroman10-bold
	lmroman10-bolditalic
	latinmodern-math)
set(LM_FONTS_HPP "${CMAKE_CURRENT_BINARY_DIR}/latinmodern-fonts.hpp")
set(_lm_header "#pragma once\n\n// Vendored Latin Modern fonts (GUST Font License; see\n// fonts/latinmodern/GUST-FONT-LICENSE.txt).\nnamespace ZShell::llm::lmfont {\n")
foreach(_lm_font IN LISTS LM_FONTS)
	string(REPLACE "-" "_" _lm_sym "${_lm_font}")
	file(READ "${CMAKE_CURRENT_SOURCE_DIR}/fonts/latinmodern/${_lm_font}.otf" _lm_hex HEX)
	string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," _lm_bytes "${_lm_hex}")
	string(APPEND _lm_header "inline const unsigned char ${_lm_sym}[] = { ${_lm_bytes} };\n")
endforeach()
string(APPEND _lm_header "}\n")
file(WRITE "${LM_FONTS_HPP}" "${_lm_header}")

qml_module(ZShell-llm
	URI ZShell.Llm
	SOURCES
		attachment.hpp attachment.cpp
		chat.hpp chat.cpp
		chatstore.hpp chatstore.cpp
		codehighlighter.hpp codehighlighter.cpp
		filetool.hpp filetool.cpp
		generation.hpp generation.cpp
		llmclient.hpp llmclient.cpp
		markdownblock.hpp
		markdownparser.hpp markdownparser.cpp
		mathtext.hpp mathtext.cpp
		message.hpp message.cpp
		messagemodel.hpp messagemodel.cpp
		segment.hpp segment.cpp
		session.hpp session.cpp
		shelltool.hpp shelltool.cpp
		tool.hpp tool.cpp
		webfetchtool.hpp webfetchtool.cpp
	LIBRARIES
		Qt::Network
		Qt::Sql
		Qt::Gui
		Qt::Widgets
		ZShell-config
		ZShell-settings
		JKQTPlotter::JKQTMathText
		PkgConfig::CMARK_GFM
		PkgConfig::TREE_SITTER
)

target_include_directories(ZShell-llm PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(ZShell-llm PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../Config)
