/* scfgsrch.c - Searchable index of SCFG menu options.
 *
 * Sysops can press Ctrl-F from the main "Configure" menu, type a substring,
 * and see every menu option whose label contains the substring. Each result
 * shows the option's label and the menu it lives in, so the sysop knows
 * where to navigate; selecting a result shows the full menu path from
 * "Configure" down to the option.
 *
 * The index data is in scfgindex.h, auto-generated by gen_option_index.py
 * from the snprintf(opt[...], ...) and uifc.list(...) calls in the
 * surrounding scfg*.c source files. Re-run that script whenever option
 * labels or menu titles change.
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#include "scfg.h"
#include "genwrap.h"
#include "scfgsrch.h"
#include "scfgindex.h"

static int icontains(const char* hay, const char* needle)
{
	if (hay == NULL || needle == NULL || *needle == '\0')
		return 1;
	size_t nlen = strlen(needle);
	for (const char* p = hay; *p; p++) {
		size_t i;
		for (i = 0; i < nlen; i++) {
			char a = (char)tolower((unsigned char)p[i]);
			char b = (char)tolower((unsigned char)needle[i]);
			if (a == 0 || a != b)
				break;
		}
		if (i == nlen)
			return 1;
	}
	return 0;
}

static size_t build_match_list(const char* query, size_t* idx, size_t idx_max)
{
	size_t k = 0;
	/* First pass: figure out the longest matching label so we can right-pad
	 * to a single column width without wasting space on short label sets.
	 * The query is matched against the option label only (matching the menu
	 * title would surface every option whose menu contains the query, which
	 * is noisy - the sysop is typically looking for the OPTION by name). */
	size_t max_label = 0;
	for (size_t i = 0; i < scfg_option_index_count; i++) {
		const scfg_option_t* o = &scfg_option_index[i];
		if (!icontains(o->label, query))
			continue;
		size_t ll = strlen(o->label);
		if (ll > max_label)
			max_label = ll;
	}
	if (max_label > MAX_OPLN - 4)
		max_label = MAX_OPLN - 4;
	for (size_t i = 0; i < scfg_option_index_count; i++) {
		const scfg_option_t* o = &scfg_option_index[i];
		if (!icontains(o->label, query))
			continue;
		if (k >= idx_max)
			break;
		idx[k] = i;
		snprintf(opt[k], MAX_OPLN, "%-*s  %s",
		         (int)max_label, o->label, o->menu);
		k++;
	}
	/* Always terminate the next slot - if k == idx_max we still need to
	 * cap the list so uifc.list doesn't walk into stale entries from a
	 * previous, longer result set. idx_max is bounded to MAX_OPTS-1 so
	 * opt[k] is in bounds. */
	opt[k][0] = '\0';
	return k;
}

/* Render an "A > B > C" path string as a CP437-line-drawing tree:
 *
 *   A
 *    \- B
 *       \- C
 *
 * (where '\-' is actually the box-drawing pair 0xC0 0xC4 - "L"-corner and
 * horizontal line - rendered together as a tree branch).
 */
static void format_path_tree(const char* path, char* dst, size_t dst_size)
{
	size_t out = strlen(dst);
	int    depth = 0;
	const char* p = path;
	while (*p && out + 1 < dst_size) {
		const char* sep = strstr(p, " > ");
		size_t      seg_len = sep ? (size_t)(sep - p) : strlen(p);
		if (depth > 0) {
			/* Indent (depth-1) * 3 spaces, then "0xC0 0xC4 ' '" connector. */
			for (int i = 0; i < (depth - 1) * 3 && out + 1 < dst_size; i++)
				dst[out++] = ' ';
			if (out + 3 < dst_size) {
				dst[out++] = (char)0xC0;  /* L corner */
				dst[out++] = (char)0xC4;  /* horizontal */
				dst[out++] = ' ';
			}
		}
		size_t copy = seg_len;
		if (out + copy + 1 >= dst_size)
			copy = (out + 1 < dst_size) ? (dst_size - out - 1) : 0;
		memcpy(dst + out, p, copy);
		out += copy;
		if (!sep)
			break;
		if (out + 1 < dst_size)
			dst[out++] = '\n';
		p = sep + 3;
		depth++;
	}
	dst[out < dst_size ? out : dst_size - 1] = '\0';
}

void scfg_option_search(void)
{
	static char query[64];
	static int  pick_dflt, pick_bar;
	size_t idx_cap = scfg_option_index_count;
	if (idx_cap > MAX_OPTS - 1)
		idx_cap = MAX_OPTS - 1;
	size_t* idx = (size_t*)malloc(sizeof(size_t) * idx_cap);
	if (idx == NULL)
		return;

	while (1) {
		uifc.helpbuf =
			"`Search Settings:`\n"
			"\n"
			"Type a word, phrase, or partial label and press ENTER. SCFG will\n"
			"list every configuration option whose label contains that\n"
			"substring (case-insensitive).\n"
			"\n"
			"Each result shows the option's label followed by the menu it lives\n"
			"in, so you know where to navigate. Selecting a result shows the\n"
			"source file/line where it is defined.\n"
			"\n"
			"Press ESC at the prompt or with an empty query to return to the\n"
			"main menu.\n"
		;
		int qlen = uifc.input(WIN_MID | WIN_SAV, 0, 0
		                      , "Find Setting"
		                      , query, 40
		                      , K_EDIT | K_LINE);
		if (qlen < 0)
			break;
		if (qlen == 0)
			break;

		size_t n = build_match_list(query, idx, idx_cap);
		if (n == 0) {
			uifc.msgf("No options match \"%s\"", query);
			continue;
		}

		while (1) {
			char title[80];
			SAFEPRINTF2(title, "Search Results: %u match%s"
			            , (uint)n, n == 1 ? "" : "es");
			uifc.helpbuf =
				"`Search Results:`\n"
				"\n"
				"Each line shows a matching option label followed by the menu\n"
				"the option lives in.\n"
				"\n"
				"Selecting a result shows the full menu path you would navigate\n"
				"from the main 'Configure' menu down to that option.\n"
				"\n"
				"Press ESC to refine the search query.\n"
			;
			/* width=0 lets UIFC auto-size to the widest option row, taking
			 * advantage of wider terminals while still fitting an 80-col
			 * window when MAX_OPLN-bounded rows are short enough. */
			int p = uifc.list(WIN_MID | WIN_SAV | WIN_ACT, 0, 0, 0
			                  , &pick_dflt, &pick_bar, title, opt);
			if (p < 0)
				break;
			if ((size_t)p >= n)
				break;
			const scfg_option_t* o = &scfg_option_index[idx[p]];
			/* Body is the tree-formatted path. The option's own label is
			 * shown in the showbuf title bar, so it doesn't need to be
			 * repeated in the body. */
			char body[1024];
			body[0] = '\0';
			format_path_tree(o->path, body, sizeof(body));
			/* Size the box to fit content. */
			int  body_lines = 1;
			int  longest = 0;
			int  cur = 0;
			for (const char* c = body; *c; c++) {
				if (*c == '\n') {
					if (cur > longest) longest = cur;
					cur = 0;
					body_lines++;
				} else {
					cur++;
				}
			}
			if (cur > longest) longest = cur;
			int title_len = (int)strlen(o->label);
			int sb_width  = (longest > title_len ? longest : title_len) + 6;
			int sb_height = body_lines + 4;
			if (sb_width < 30) sb_width = 30;
			int show_cur = 0, show_bar = 0;
			uifc.showbuf(WIN_MID, 0, 0, sb_width, sb_height
			             , o->label, body, &show_cur, &show_bar);
		}
	}
	free(idx);
}
