/* $OpenBSD: prompt-history.c,v 1.2 2026/07/13 19:35:22 nicm Exp $ */

/*
 * Copyright (c) 2026 Nicholas Marriott <nicholas.marriott@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/types.h>

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

#include "tmux.h"

static char	*prompt_find_history_file(void);
static void	 prompt_add_typed_history(char *);

/* Prompt history. */
static char	**prompt_hlist[PROMPT_NTYPES];
static u_int	  prompt_hsize[PROMPT_NTYPES];

/* Find the history file to load/save from/to. */
static char *
prompt_find_history_file(void)
{
	const char	*home, *history_file;
	char		*path;

	history_file = options_get_string(global_options, "history-file");
	if (*history_file == '\0')
		return (NULL);
	if (*history_file == '/')
		return (xstrdup(history_file));

	if (history_file[0] != '~' || history_file[1] != '/')
		return (NULL);
	if ((home = find_home()) == NULL)
		return (NULL);
	xasprintf(&path, "%s%s", home, history_file + 1);
	return (path);
}

/* Add loaded history item to the appropriate list. */
static void
prompt_add_typed_history(char *line)
{
	char			*typestr;
	enum prompt_type	 type = PROMPT_TYPE_INVALID;

	typestr = strsep(&line, ":");
	if (line != NULL)
		type = prompt_type(typestr);
	if (type == PROMPT_TYPE_INVALID) {
		/*
		 * Invalid types are not expected, but this provides backward
		 * compatibility with old history files.
		 */
		if (line != NULL)
			*(--line) = ':';
		prompt_add_history(typestr, PROMPT_TYPE_COMMAND);
	} else
		prompt_add_history(line, type);
}

/* Load prompt history from file. */
void
prompt_load_history(void)
{
	FILE	*f;
	char	*history_file, *line = NULL;
	size_t	 length = 0;
	ssize_t	 got;

	if ((history_file = prompt_find_history_file()) == NULL)
		return;
	log_debug("loading history from %s", history_file);

	f = fopen(history_file, "r");
	if (f == NULL) {
		log_debug("%s: %s", history_file, strerror(errno));
		free(history_file);
		return;
	}
	free(history_file);

	while ((got = getline(&line, &length, f)) != -1) {
		if (got > 0 && line[got - 1] == '\n')
			line[got - 1] = '\0';
		if (got > 0)
			prompt_add_typed_history(line);
	}
	free(line);
	fclose(f);
}

/* Save prompt history to file. */
void
prompt_save_history(void)
{
	FILE	*f;
	u_int	 i, type;
	char	*history_file;

	if ((history_file = prompt_find_history_file()) == NULL)
		return;
	log_debug("saving history to %s", history_file);

	f = fopen(history_file, "w");
	if (f == NULL) {
		log_debug("%s: %s", history_file, strerror(errno));
		free(history_file);
		return;
	}
	free(history_file);

	for (type = 0; type < PROMPT_NTYPES; type++) {
		for (i = 0; i < prompt_hsize[type]; i++) {
			fputs(prompt_type_string(type), f);
			fputc(':', f);
			fputs(prompt_hlist[type][i], f);
			fputc('\n', f);
		}
	}
	fclose(f);

}

/* Get previous line from the history. */
const char *
prompt_up_history(u_int *idx, u_int type)
{
	/*
	 * History runs from 0 to size - 1. Index is from 0 to size. Zero is
	 * empty.
	 */

	if (type >= PROMPT_NTYPES)
		return (NULL);
	if (prompt_hsize[type] == 0 || idx[type] == prompt_hsize[type])
		return (NULL);
	idx[type]++;
	return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
}

/* Get next line from the history. */
const char *
prompt_down_history(u_int *idx, u_int type)
{
	if (type >= PROMPT_NTYPES)
		return ("");
	if (prompt_hsize[type] == 0 || idx[type] == 0)
		return ("");
	idx[type]--;
	if (idx[type] == 0)
		return ("");
	return (prompt_hlist[type][prompt_hsize[type] - idx[type]]);
}

/* Add line to the history. */
void
prompt_add_history(const char *line, u_int type)
{
	u_int	i, oldsize, newsize, freecount, hlimit, new = 1;
	size_t	movesize;

	if (type >= PROMPT_NTYPES)
		return;

	oldsize = prompt_hsize[type];
	if (oldsize > 0 &&
	    strcmp(prompt_hlist[type][oldsize - 1], line) == 0)
		new = 0;

	hlimit = options_get_number(global_options, "prompt-history-limit");
	if (hlimit > oldsize) {
		if (new == 0)
			return;
		newsize = oldsize + new;
	} else {
		newsize = hlimit;
		freecount = oldsize + new - newsize;
		if (freecount > oldsize)
			freecount = oldsize;
		if (freecount == 0)
			return;
		for (i = 0; i < freecount; i++)
			free(prompt_hlist[type][i]);
		movesize = (oldsize - freecount) *
		    sizeof *prompt_hlist[type];
		if (movesize > 0) {
			memmove(&prompt_hlist[type][0],
			    &prompt_hlist[type][freecount], movesize);
		}
	}

	if (newsize == 0) {
		free(prompt_hlist[type]);
		prompt_hlist[type] = NULL;
	} else if (newsize != oldsize) {
		prompt_hlist[type] =
		    xreallocarray(prompt_hlist[type], newsize,
			sizeof *prompt_hlist[type]);
	}

	if (new == 1 && newsize > 0)
		prompt_hlist[type][newsize - 1] = xstrdup(line);
	prompt_hsize[type] = newsize;
}

/* Get history size. */
u_int
prompt_history_size(enum prompt_type type)
{
	if (type >= PROMPT_NTYPES)
		return (0);
	return (prompt_hsize[type]);
}

/* Get history entry. */
const char *
prompt_history_get(enum prompt_type type, u_int idx)
{
	if (type >= PROMPT_NTYPES)
		return (NULL);
	if (idx >= prompt_hsize[type])
		return (NULL);
	return (prompt_hlist[type][idx]);
}

/* Clear prompt history. */
void
prompt_history_clear(enum prompt_type type)
{
	u_int	idx;

	if (type >= PROMPT_NTYPES)
		return;
	for (idx = 0; idx < prompt_hsize[type]; idx++)
		free(prompt_hlist[type][idx]);
	free(prompt_hlist[type]);
	prompt_hlist[type] = NULL;
	prompt_hsize[type] = 0;
}
