/* ************************************************************************** */
/*                                                                            */
/*                                                        :::      ::::::::   */
/*   executor.c                                         :+:      :+:    :+:   */
/*                                                    +:+ +:+         +:+     */
/*   By: sede-san <sede-san@student.42madrid.com    +#+  +:+       +#+        */
/*                                                +#+#+#+#+#+   +#+           */
/*   Created: 2026/02/08 19:10:47 by sede-san          #+#    #+#             */
/*   Updated: 2026/02/08 21:32:03 by sede-san         ###   ########.fr       */
/*                                                                            */
/* ************************************************************************** */

#include "executor.h"
#include "builtins.h"
#include <errno.h>

static bool		is_path_explicit(const char *command_name);
static char		*resolve_path_from_env(const char *command_name,
					t_minishell *minishell);
static char		*resolve_command_path(const t_command *command,
					t_minishell *minishell);
static uint8_t	resolve_execve_status(void);
static inline uint8_t	execute_builtin(
	const t_command *command,
	t_minishell *minishell
)
{
	const t_builtin_func	builtin
		= ft_hashmap_get(minishell->builtins, command->argv[0]);

	return (builtin(*command, minishell));
}

static void	handle_execve_error(
	const t_command *command,
	char **envp
)
{
	const uint8_t	exit_status = resolve_execve_status();

	free_envp(envp);
	perror(command->path);
	exit(exit_status);
}

static void	execute_external_command(
	const t_command *command,
	t_minishell *minishell
)
{
	char	**envp;

	envp = get_envp(minishell);
	if (envp == NULL)
	{
		perror("get_envp");
		exit(EXIT_FAILURE);
	}
	execve(command->path, command->argv, envp);
	handle_execve_error(command, envp);
}

static bool	is_path_explicit(
	const char *command_name
)
{
	return (command_name != NULL && ft_strchr(command_name, '/') != NULL);
}

static char	*resolve_path_from_env(
	const char *command_name,
	t_minishell *minishell
)
{
	char	*command_path;
	char	**path_env;
	char	*path_value;
	size_t	i;

	path_value = get_env("PATH", minishell);
	if (path_value == NULL)
		return (NULL);
	path_env = ft_split(path_value, ':');
	if (path_env == NULL)
		return (NULL);
	command_path = NULL;
	i = -1;
	while (!command_path && path_env[++i] != NULL)
	{
		command_path = ft_strnjoin(3, path_env[i], "/", command_name);
		if (command_path != NULL && access(command_path, X_OK) != EXIT_SUCCESS)
		{
			free(command_path);
			command_path = NULL;
		}
	}
	ft_free_split(path_env);
	return (command_path);
}

static char	*resolve_command_path(
	const t_command *command,
	t_minishell *minishell
)
{
	const char	*command_name;
	char		*command_path;

	if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
		return (NULL);
	command_name = command->argv[0];
	if (is_path_explicit(command_name)
		&& access(command_name, X_OK) == EXIT_SUCCESS)
		return (ft_strdup(command_name));
	command_path = resolve_path_from_env(command_name, minishell);
	return (command_path);
}

static uint8_t	resolve_execve_status(void)
{
	if (errno == ENOENT)
		return (127);
	if (errno == EACCES || errno == ENOEXEC || errno == EISDIR)
		return (126);
	return (EXIT_FAILURE);
}

static uint8_t	execute_command(
	t_command *command,
	t_minishell *minishell
)
{
	char	*resolved_path;

	if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
		return (EXIT_SUCCESS);
	if (is_builtin(command->argv[0], minishell))
		return (execute_builtin(command, minishell));
	resolved_path = resolve_command_path(command, minishell);
	if (resolved_path == NULL)
	{
		ft_eprintf("minishell: %s: command not found\n", command->argv[0]);
		return (127);
	}
	command->path = resolved_path;
	execute_external_command(command, minishell);
	return (EXIT_FAILURE); //! should never reach here
}

static bool	is_builtin_command(
	const t_command *command,
	t_minishell *minishell
)
{
	if (command == NULL || command->argv == NULL || command->argv[0] == NULL)
		return (false);
	return (is_builtin(command->argv[0], minishell));
}

static void	cmdfree_argv(
	char **argv
)
{
	size_t	i;

	if (argv == NULL)
	{
		return ;
	}
	i = 0;
	while (argv[i] != NULL)
	{
		free(argv[i]);
		i++;
	}
	free(argv);
}

static void	cmdfree(
	t_command *command
)
{
	if (command == NULL)
		return ;
	cmdfree_argv(command->argv);
	free(command->path);
	free(command);
}

static int	create_pipe_if_needed(
	t_list *current_command,
	t_pipeline *pipeline
)
{
	if (!current_command->next)
		return (0);
	if (pipe(pipeline->pipefd) == PIPE_ERROR)
		return (perror("pipe"), PIPE_ERROR);
	return (0);
}

static bool	is_fork_required(
	t_list *current_command,
	const t_pipeline *pipeline,
	t_minishell *minishell
) {
	const t_command	*command = current_command->content;

	return (pipeline->prev_read_fd != -1
		|| current_command->next != NULL
		|| !is_builtin_command(command, minishell));
}

static void	setup_child_input(
	t_pipeline *pipeline
)
{
	if (pipeline->prev_read_fd != -1)
	{
		dup2(pipeline->prev_read_fd, STDIN_FILENO);
		close(pipeline->prev_read_fd);
	}
}

static void	setup_child_output(
	t_list *current_command,
	t_pipeline *pipeline
)
{
	if (current_command->next)
	{
		dup2(pipeline->pipefd[WRITE_PIPE], STDOUT_FILENO);
		close(pipeline->pipefd[READ_PIPE]);
		close(pipeline->pipefd[WRITE_PIPE]);
	}
}

static void	child_process(
	t_list *current_command,
	t_pipeline *pipeline,
	t_minishell *minishell
)
{
	uint8_t			exit_status;
	t_command		*command;

	command = current_command->content;
	setup_child_input(pipeline);
	setup_child_output(current_command, pipeline);
	exit_status = execute_command(command, minishell);
	exit(exit_status);
}

static void	parent_cleanup(
	t_list *current_command,
	t_pipeline *pipeline
)
{
	if (pipeline->prev_read_fd != -1)
		close(pipeline->prev_read_fd);
	if (current_command->next)
	{
		close(pipeline->pipefd[WRITE_PIPE]);
		pipeline->prev_read_fd = pipeline->pipefd[READ_PIPE];
	}
	else
		pipeline->prev_read_fd = -1;
}

static uint8_t	wait_for_children(
	pid_t last_child_pid
)
{
	uint8_t	exit_status;
	int		status;
	pid_t	pid;

	exit_status = EXIT_SUCCESS;
	while (last_child_pid > 0 && (pid = wait(&status)) > 0)
	{
		if (pid == last_child_pid)
		{
			if (WIFEXITED(status))
				exit_status = WEXITSTATUS(status);
			else if (WIFSIGNALED(status))
				exit_status = 128 + WTERMSIG(status);
		}
	}
	return (exit_status);
}

uint8_t	execute(
	t_list *command_list,
	t_minishell *minishell
)
{
	uint8_t		exit_status;
	t_pipeline	pipeline;
	t_list		*current_command;
	pid_t		pid;
	pid_t		last_child_pid;
	t_command	*command;
	bool		should_fork;

	pipeline.prev_read_fd = -1;
	exit_status = EXIT_SUCCESS;
	last_child_pid = -1;
	current_command = command_list;
	while (current_command)
	{
		if (create_pipe_if_needed(current_command, &pipeline) == PIPE_ERROR)
		{
			exit_status = EXIT_FAILURE;
			break ;
		}
		should_fork = is_fork_required(current_command, &pipeline, minishell);
		if (should_fork)
		{
			pid = fork();
			if (pid == FORK_ERROR)
			{
				perror("fork");
				exit_status = EXIT_FAILURE;
				break ;
			}
			if (pid == 0)
				child_process(current_command, &pipeline, minishell);
			last_child_pid = pid;
		}
		else
		{
			command = current_command->content;
			exit_status = execute_command(command, minishell);
		}
		parent_cleanup(current_command, &pipeline);
		current_command = current_command->next;
	}
	if (last_child_pid > 0)
		exit_status = wait_for_children(last_child_pid);
	minishell->exit_status = exit_status;
	ft_lstclear(&command_list, (void (*)(void *))cmdfree);
	return (exit_status);
}
