/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   parser.c                                           :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: sede-san <sede-san@student.42madrid.com    +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2025/10/22 18:37:38 by sede-san          #+#    #+#             */
/*   Updated: 2026/02/12 21:25:39 by sede-san         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "parser.h"
#include "errors.h"

static t_list *parse_tokens(
	t_list *tokens
);

static t_list	*ft_lstfind(
	t_list *lst,
	bool (*pre)(void *)
);

static void expand_variable(
	char **argument,
	int *i,
	t_minishell *minishell
);

static void	expand(
	t_list **commands,
	t_minishell *minishell
);


/**
 * @brief Converts a command line string into a list of commands.
 *
 * @param line      The command line string to parse.
 * @param minishell The minishell instance.
 *
 * @return A list of commands or `NULL` on error.
 */
t_list *parse(
	char *line,
	t_minishell *minishell
)
{
	t_list *commands;
	t_list *tokens;

	if (line == NULL)
		return (NULL);
	tokens = lex(line);
	commands = parse_tokens(tokens);
	ft_lstclear(&tokens, (void (*)(void *))token_clear);
	expand(&commands, minishell);
	return (commands);
}

/**
 * @brief Converts a list of tokens into a list of commands.
 *
 * @param tokens    The list of tokens to parse.
 * @param minishell The minishell instance.
 *
 * @return A list of commands or `NULL` on error.
 */
static t_list *parse_tokens(
	t_list *tokens
)
{
	t_list		*commands;
	t_command	*command;
	t_list		*current_token;
	t_list		*new_command;

	if (tokens == NULL)
		return (NULL);
	commands = NULL;
	current_token = tokens;
	while (current_token != NULL)
	{
		command = command_new(&current_token);
		if (command == NULL)
		{
			ft_lstclear(&commands, (void (*)(void *))command_clear);
			return (NULL);
		}
		new_command = ft_lstnew(command);
		if (new_command == NULL)
		{
			command_clear(command);
			ft_lstclear(&commands, (void (*)(void *))command_clear);
			return (malloc_error(), NULL);
		}
		ft_lstadd_back(&commands, new_command);
		if (current_token != NULL)
		{
			if (current_token->next == NULL)
				syntax_error_unexpected_token((t_token *)current_token->content);
			current_token = current_token->next;
		}
	}
	return (commands);
}

static void expand_variable(
	char **argument,
	int *i,
	t_minishell *minishell
)
{
	char		*expanded;
	char		*variable;
	char		*variable_name;
	const int	start = *i + 1;
	int			end;

	end = start;
	while (ft_isalnum((*argument)[end]))
		end++;
	variable_name = ft_substr(*argument, start, end - start);
	if (variable_name == NULL)
		return (minishell->exit = true, malloc_error());
	variable = get_env(variable_name, minishell);
	free(variable_name);
	if (variable == NULL)
		variable = "";
	expanded = ft_strnjoin(3,
		ft_substr(*argument, 0, *i),
		variable,
		ft_substr(*argument,end, ft_strlen(*argument) - end));
	if (expanded == NULL)
		return (minishell->exit = true, malloc_error());
	*i += ft_strlen(variable) - 1;
	free(*argument);
	*argument = expanded;
}

static void expand_argument(
	char **argument,
	t_minishell *minishell
)
{
	bool	in_single_quote;
	bool	in_double_quote;
	int		i;
	
	in_single_quote = false;
	in_double_quote = false;
	i = 0;
	while ((*argument)[i] != '\0')
	{
		if ((*argument)[i] == '$' && !in_single_quote)
		{
			expand_variable(argument, &i, minishell);
			if (*argument == NULL)
				return (minishell->exit = true, malloc_error());
		}
		else
		{
			if ((*argument)[i] == '\'' && !in_double_quote)
				in_single_quote = !in_single_quote;
			else if ((*argument)[i] == '"' && !in_single_quote)
				in_double_quote = !in_double_quote;
			i++;
		}
	}
}

static void	expand(
	t_list **commands,
	t_minishell *minishell
)
{
	t_list		*current_command;
	t_command	*command;
	int			i;
	
	current_command = *commands;
	while (current_command != NULL)
	{
		command = (t_command *)current_command->content;
		i = 0;
		while (i < command->argc)
		{
			expand_argument(&command->argv[i], minishell);
			if (command->argv[i] == NULL)
				ft_lstclear(commands, (void (*)(void *))command_clear);
			i++;
		}
		if (command == NULL)
			return (ft_lstclear(commands, (void (*)(void *))command_clear));
		current_command = current_command->next;
	}
}

/**
 * @brief Creates a new command from a list of tokens.
 *
 * @param tokens The list of tokens to create the command from.
 *
 * @return A new command or NULL on error.
 *
 * @note The `tokens` pointer is moved to the next command's tokens.
 */
t_command *command_new(
	t_list **tokens
)
{
	t_command *command;
	t_list *current_token;
	t_list *delimiter_token;

	command = (t_command *)ft_calloc(1, sizeof(t_command));
	if (command == NULL)
		return (NULL);
	current_token = *tokens;
	delimiter_token = ft_lstfind(current_token, (bool (*)(void *))is_pipe);
	while (command != NULL && current_token != delimiter_token)
	{
		command_add_tokens(&command, &current_token);
	}
	*tokens = current_token;
	return (command);
}

/**
 * @brief Creates a new redirection from a list of tokens.
 *
 * @param tokens The list of tokens to create the redirection from.
 *
 * @return A new redirection or `NULL` on error.
 */
t_redirection *redirection_new(
	t_list **tokens
)
{
	t_redirection	*redirection;
	t_token			*token;

	redirection = (t_redirection *)malloc(sizeof(t_redirection));
	if (redirection == NULL)
		return (malloc_error(), NULL);
	token = (t_token *)(*tokens)->content;
	redirection->type = token->type;
	*tokens = (*tokens)->next;
	if (*tokens == NULL)
	{
		free(redirection);
		return (syntax_error_unexpected_token(NULL), NULL);
	}
	token = (t_token *)(*tokens)->content;
	if (token->type != TOKEN_WORD)
	{
		free(redirection);
		while (*tokens != NULL)
			*tokens = (*tokens)->next;
		return (syntax_error_unexpected_token(token), NULL);
	}
	redirection->target = ft_strdup(token->value);
	if (redirection->target == NULL)
	{
		free(redirection);
		return (malloc_error(), NULL);
	}
	*tokens = (*tokens)->next;
	return (redirection);
}

void redirection_clear(
	t_redirection *redirection
)
{
	if (redirection != NULL)
	{
		free(redirection->target);
		free(redirection);
	}
}

/**
 * @brief Adds a token to a command, updating the command's arguments and
 *        redirections as necessary.
 *
 * @param command The command to add the token to.
 * @param tokens  The list of tokens to add to the command.
 *
 * @note The `command` pointer can be free'd if there is an error while adding
 *       the token.
 */
void command_add_tokens(
	t_command **command,
	t_list **tokens
)
{
	t_token *token;

	token = (t_token *)(*tokens)->content;
	if (is_redirection(token))
		redirection_add(tokens, token, command);
	else
		words_add(tokens, command);
}

/**
 * @brief Converts a list of arguments into an array.
 * 
 * @param args The list of arguments to convert.
 * @param argc The number of arguments in the list.
 * 
 * @return An array of arguments or `NULL` on error.
 * 
 * @note The `args` list is cleared after the conversion and set to NULL.
 */
char **args_to_array(
	t_list **args,
	size_t argc
)
{
	char	**argv;
	t_list	*arg;
	size_t	i;

	argv = (char **)malloc(sizeof(char *) * (argc + 1));
	if (argv == NULL)
		return (NULL);
	i = 0;
	arg = *args;
	while (arg != NULL)
	{
		argv[i] = (char *)arg->content;
		arg = arg->next;
		i++;
	}
	argv[i] = NULL;
	ft_lstclear_nodes(args);
	return (argv);
}

/**
 * @brief Adds all consecutive word tokens to a command's argv and updates its
 *        argc accordingly.
 * 
 * @param command The command to add the word tokens to.
 * @param tokens  The list of tokens to add to the command.
 */
void words_add(
	t_list **tokens,
	t_command **command
)
{
	t_list *args;
	t_list *arg;
	t_token *token;

	args = NULL;
	arg = *tokens;
	token = (t_token *)arg->content;
	while (arg != NULL && token->type == TOKEN_WORD)
	{
		ft_lstadd_back(&args, ft_lstnew(ft_strdup(token->value)));
		(*command)->argc++;
		arg = arg->next;
		if (arg != NULL)
			token = (t_token *)arg->content;
	}
	*tokens = arg;
	(*command)->argv = args_to_array(&args, (*command)->argc);
	ft_lstclear_nodes(&args);
}

void redirection_add(
	t_list **tokens,
	t_token *token,
	t_command **command
)
{
	t_redirection	*redirection;
	t_list			*redirection_tokens;

	redirection = redirection_new(tokens);
	if (redirection == NULL)
	{
		command_clear(*command);
		*command = NULL;
		return ;
	}
	redirection_tokens = ft_lstnew(redirection);
	if (redirection_tokens == NULL)
	{
		free(redirection);
		return (malloc_error());
	}
	if (token->type == TOKEN_HEREDOC)
		ft_lstadd_back(&(*command)->heredocs, redirection_tokens);
	else
		ft_lstadd_back(&(*command)->redirections, redirection_tokens);
}

/**
 * @brief Checks if a token is a redirection token.
 *
 * @param token The token to check.
 *
 * @return `true` if the token is a redirection token, `false` otherwise.
 */
bool is_redirection(
	t_token *token
)
{
	return (token->type == TOKEN_REDIRECT_IN
		|| token->type == TOKEN_REDIRECT_OUT
		|| token->type == TOKEN_APPEND
		|| token->type == TOKEN_HEREDOC);
}

void command_clear_argv(
	t_command *command
)
{
	int	i;

	if (command->argv != NULL)
	{
		i = 0;
		while (i < command->argc)
		{
			free(command->argv[i]);
			i++;
		}
		free(command->argv);
		command->argv = NULL;
	}
}

/**
 * @brief Clears a command, freeing all associated memory.
 *
 * @param command The command to clear.
 */
void command_clear(
	t_command *command
)
{
	if (command != NULL)
	{
		command_clear_argv(command);
		ft_lstclear(&command->redirections, (void (*)(void *))redirection_clear);
		ft_lstclear(&command->heredocs, (void (*)(void *))redirection_clear);
		free(command);
	}
}

/**
 * @brief Checks if a token is a pipe token.
 *
 * @param token The token to check.
 *
 * @return `true` if the token is a pipe token, `false` otherwise.
 */
bool is_pipe(
	t_token *token)
{
	return (token->type == TOKEN_PIPE);
}

/**
 * @brief Finds a node in a linked list that satisfies a given predicate.
 *
 * @param lst The linked list to search through.
 * @param pre   The predicate function to apply to each node's content.
 *
 * @returns The first node that satisfies the predicate or `NULL` if no such
 *          node exists or if the list is `NULL`.
 */
t_list *ft_lstfind(
	t_list *lst,
	bool (*pre)(void *))
{
	while (lst != NULL)
	{
		if (pre(lst->content))
			return (lst);
		lst = lst->next;
	}
	return (NULL);
}
