<?php

namespace App\Livewire\Project\Application;

use App\Actions\Application\GenerateConfig;
use App\Jobs\ApplicationDeploymentJob;
use App\Models\Application;
use App\Rules\ValidGitBranch;
use App\Support\ValidationPatterns;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
use Livewire\Features\SupportEvents\Event;

class General extends Component
{
    use AuthorizesRequests;

    public string $applicationId;

    public Application $application;

    public Collection $services;

    public string $name;

    public ?string $description = null;

    public ?string $fqdn = null;

    public string $gitRepository;

    public string $gitBranch;

    public ?string $gitCommitSha = null;

    public ?string $installCommand = null;

    public ?string $buildCommand = null;

    public ?string $startCommand = null;

    public string $buildPack;

    public string $staticImage;

    public string $baseDirectory;

    public ?string $publishDirectory = null;

    public ?string $portsExposes = null;

    public ?string $portsMappings = null;

    public ?string $customNetworkAliases = null;

    public ?string $dockerfile = null;

    public ?string $dockerfileLocation = null;

    public ?string $dockerfileTargetBuild = null;

    public ?string $dockerRegistryImageName = null;

    public ?string $dockerRegistryImageTag = null;

    public ?string $dockerComposeLocation = null;

    public ?string $dockerCompose = null;

    public ?string $dockerComposeRaw = null;

    public ?string $dockerComposeCustomStartCommand = null;

    public ?string $dockerComposeCustomBuildCommand = null;

    public ?string $customDockerRunOptions = null;

    // Security: pre/post deployment commands are intentionally arbitrary shell — users need full
    // flexibility (e.g. "php artisan migrate"). Access is gated by team authentication/authorization.
    // Commands execute inside the application's own container, not on the host.
    public ?string $preDeploymentCommand = null;

    public ?string $preDeploymentCommandContainer = null;

    public ?string $postDeploymentCommand = null;

    public ?string $postDeploymentCommandContainer = null;

    public ?string $customNginxConfiguration = null;

    public bool $isStatic = false;

    public bool $isSpa = false;

    public bool $isBuildServerEnabled = false;

    public bool $isPreserveRepositoryEnabled = false;

    public bool $isContainerLabelEscapeEnabled = true;

    public bool $isContainerLabelReadonlyEnabled = false;

    public bool $isHttpBasicAuthEnabled = false;

    public ?string $httpBasicAuthUsername = null;

    public ?string $httpBasicAuthPassword = null;

    public ?string $watchPaths = null;

    public string $redirect;

    public $customLabels;

    public bool $labelsChanged = false;

    public bool $initLoadingCompose = false;

    public ?string $initialDockerComposeLocation = null;

    public ?Collection $parsedServices;

    public $parsedServiceDomains = [];

    public $domainConflicts = [];

    public $showDomainConflictModal = false;

    public $forceSaveDomains = false;

    protected $listeners = [
        'resetDefaultLabels',
        'configurationChanged' => '$refresh',
        'confirmDomainUsage',
    ];

    protected function rules(): array
    {
        return [
            'name' => ValidationPatterns::nameRules(),
            'description' => ValidationPatterns::descriptionRules(),
            'fqdn' => ValidationPatterns::applicationDomainRules(),
            'parsedServiceDomains.*.domain' => ValidationPatterns::applicationDomainRules(),
            'gitRepository' => 'required',
            'gitBranch' => ['required', 'string', new ValidGitBranch],
            'gitCommitSha' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._\-\/]*$/'],
            'installCommand' => ValidationPatterns::shellSafeCommandRules(),
            'buildCommand' => ValidationPatterns::shellSafeCommandRules(),
            'startCommand' => ValidationPatterns::shellSafeCommandRules(),
            'buildPack' => 'required',
            'staticImage' => 'required',
            'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)),
            'publishDirectory' => ValidationPatterns::directoryPathRules(),
            'portsExposes' => ['nullable', 'string', 'regex:/^(\d+)(,\d+)*$/'],
            'portsMappings' => ValidationPatterns::portMappingRules(),
            'customNetworkAliases' => 'nullable',
            'dockerfile' => 'nullable',
            'dockerRegistryImageName' => ValidationPatterns::dockerImageNameRules(),
            'dockerRegistryImageTag' => ValidationPatterns::dockerImageTagRules(),
            'dockerfileLocation' => ValidationPatterns::filePathRules(),
            'dockerComposeLocation' => ValidationPatterns::filePathRules(),
            'dockerCompose' => 'nullable',
            'dockerComposeRaw' => 'nullable',
            'dockerfileTargetBuild' => ValidationPatterns::dockerTargetRules(),
            'dockerComposeCustomStartCommand' => ValidationPatterns::shellSafeCommandRules(),
            'dockerComposeCustomBuildCommand' => ValidationPatterns::shellSafeCommandRules(),
            'customLabels' => 'nullable',
            'customDockerRunOptions' => ValidationPatterns::shellSafeCommandRules(2000),
            'preDeploymentCommand' => 'nullable',
            'preDeploymentCommandContainer' => ['nullable', ...ValidationPatterns::containerNameRules()],
            'postDeploymentCommand' => 'nullable',
            'postDeploymentCommandContainer' => ['nullable', ...ValidationPatterns::containerNameRules()],
            'customNginxConfiguration' => 'nullable',
            'isStatic' => 'boolean|required',
            'isSpa' => 'boolean|required',
            'isBuildServerEnabled' => 'boolean|required',
            'isContainerLabelEscapeEnabled' => 'boolean|required',
            'isContainerLabelReadonlyEnabled' => 'boolean|required',
            'isPreserveRepositoryEnabled' => 'boolean|required',
            'isHttpBasicAuthEnabled' => 'boolean|required',
            'httpBasicAuthUsername' => 'string|nullable',
            'httpBasicAuthPassword' => 'string|nullable',
            'watchPaths' => 'nullable',
            'redirect' => 'string|required',
        ];
    }

    protected function messages(): array
    {
        return array_merge(
            ValidationPatterns::combinedMessages(),
            [
                ...ValidationPatterns::filePathMessages('dockerfileLocation', 'Dockerfile'),
                ...ValidationPatterns::filePathMessages('dockerComposeLocation', 'Docker Compose'),
                'baseDirectory.regex' => 'The base directory must be a valid path starting with / and containing only safe characters.',
                'publishDirectory.regex' => 'The publish directory must be a valid path starting with / and containing only safe characters.',
                'dockerfileTargetBuild.regex' => 'The Dockerfile target build must contain only alphanumeric characters, dots, hyphens, and underscores.',
                'dockerComposeCustomStartCommand.regex' => 'The Docker Compose start command contains invalid characters. Allowed: alphanumerics, && / || chaining, balanced quotes, globs (*, ?), !, and safe path/arg chars. Blocked: bare &, bare |, ;, $, backtick, (, ), <, >, \\, newlines.',
                'dockerComposeCustomBuildCommand.regex' => 'The Docker Compose build command contains invalid characters. Allowed: alphanumerics, && / || chaining, balanced quotes, globs (*, ?), !, and safe path/arg chars. Blocked: bare &, bare |, ;, $, backtick, (, ), <, >, \\, newlines.',
                'customDockerRunOptions.regex' => 'The custom Docker run options contain invalid characters. Allowed: alphanumerics, && / || chaining, balanced quotes, globs (*, ?), !, and safe path/arg chars. Blocked: bare &, bare |, ;, $, backtick, (, ), <, >, \\, newlines.',
                'installCommand.regex' => 'The install command contains invalid characters. Allowed: alphanumerics, && / || chaining, balanced quotes, globs (*, ?), !, and safe path/arg chars. Blocked: bare &, bare |, ;, $, backtick, (, ), <, >, \\, newlines.',
                'buildCommand.regex' => 'The build command contains invalid characters. Allowed: alphanumerics, && / || chaining, balanced quotes, globs (*, ?), !, and safe path/arg chars. Blocked: bare &, bare |, ;, $, backtick, (, ), <, >, \\, newlines.',
                'startCommand.regex' => 'The start command contains invalid characters. Allowed: alphanumerics, && / || chaining, balanced quotes, globs (*, ?), !, and safe path/arg chars. Blocked: bare &, bare |, ;, $, backtick, (, ), <, >, \\, newlines.',
                'preDeploymentCommandContainer.regex' => 'The pre-deployment command container name must contain only alphanumeric characters, dots, hyphens, and underscores.',
                'postDeploymentCommandContainer.regex' => 'The post-deployment command container name must contain only alphanumeric characters, dots, hyphens, and underscores.',
                'name.required' => 'The Name field is required.',
                'gitRepository.required' => 'The Git Repository field is required.',
                'gitBranch.required' => 'The Git Branch field is required.',
                'buildPack.required' => 'The Build Pack field is required.',
                'staticImage.required' => 'The Static Image field is required.',
                'baseDirectory.required' => 'The Base Directory field is required.',
                'portsExposes.regex' => 'Ports exposes must be a comma-separated list of port numbers (e.g. 3000,3001).',
                ...ValidationPatterns::portMappingMessages(),
                'isStatic.required' => 'The Static setting is required.',
                'isStatic.boolean' => 'The Static setting must be true or false.',
                'isSpa.required' => 'The SPA setting is required.',
                'isSpa.boolean' => 'The SPA setting must be true or false.',
                'isBuildServerEnabled.required' => 'The Build Server setting is required.',
                'isBuildServerEnabled.boolean' => 'The Build Server setting must be true or false.',
                'isContainerLabelEscapeEnabled.required' => 'The Container Label Escape setting is required.',
                'isContainerLabelEscapeEnabled.boolean' => 'The Container Label Escape setting must be true or false.',
                'isContainerLabelReadonlyEnabled.required' => 'The Container Label Readonly setting is required.',
                'isContainerLabelReadonlyEnabled.boolean' => 'The Container Label Readonly setting must be true or false.',
                'isPreserveRepositoryEnabled.required' => 'The Preserve Repository setting is required.',
                'isPreserveRepositoryEnabled.boolean' => 'The Preserve Repository setting must be true or false.',
                'isHttpBasicAuthEnabled.required' => 'The HTTP Basic Auth setting is required.',
                'isHttpBasicAuthEnabled.boolean' => 'The HTTP Basic Auth setting must be true or false.',
                'redirect.required' => 'The Redirect setting is required.',
                'redirect.string' => 'The Redirect setting must be a string.',
            ]
        );
    }

    protected $validationAttributes = [
        'name' => 'name',
        'description' => 'description',
        'fqdn' => 'FQDN',
        'gitRepository' => 'Git repository',
        'gitBranch' => 'Git branch',
        'gitCommitSha' => 'Git commit SHA',
        'installCommand' => 'Install command',
        'buildCommand' => 'Build command',
        'startCommand' => 'Start command',
        'buildPack' => 'Build pack',
        'staticImage' => 'Static image',
        'baseDirectory' => 'Base directory',
        'publishDirectory' => 'Publish directory',
        'portsExposes' => 'Ports exposes',
        'portsMappings' => 'Ports mappings',
        'dockerfile' => 'Dockerfile',
        'dockerRegistryImageName' => 'Docker registry image name',
        'dockerRegistryImageTag' => 'Docker registry image tag',
        'dockerfileLocation' => 'Dockerfile location',
        'dockerComposeLocation' => 'Docker compose location',
        'dockerCompose' => 'Docker compose',
        'dockerComposeRaw' => 'Docker compose raw',
        'customLabels' => 'Custom labels',
        'dockerfileTargetBuild' => 'Dockerfile target build',
        'customDockerRunOptions' => 'Custom docker run commands',
        'customNetworkAliases' => 'Custom docker network aliases',
        'dockerComposeCustomStartCommand' => 'Docker compose custom start command',
        'dockerComposeCustomBuildCommand' => 'Docker compose custom build command',
        'customNginxConfiguration' => 'Custom Nginx configuration',
        'isStatic' => 'Is static',
        'isSpa' => 'Is SPA',
        'isBuildServerEnabled' => 'Is build server enabled',
        'isContainerLabelEscapeEnabled' => 'Is container label escape enabled',
        'isContainerLabelReadonlyEnabled' => 'Is container label readonly',
        'isPreserveRepositoryEnabled' => 'Is preserve repository enabled',
        'watchPaths' => 'Watch paths',
        'redirect' => 'Redirect',
    ];

    public function mount()
    {
        try {
            $this->parsedServices = $this->application->parse();
            if (is_null($this->parsedServices) || empty($this->parsedServices)) {
                $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
                // Still sync data even if parse fails, so form fields are populated
                $this->syncData();

                return;
            }
        } catch (\Throwable $e) {
            $this->dispatch('error', $e->getMessage());
            // Still sync data even on error, so form fields are populated
            $this->syncData();
        }
        if ($this->application->build_pack === 'dockercompose') {
            // Only update if user has permission
            try {
                $this->authorize('update', $this->application);
                $this->application->fqdn = null;
                $this->application->settings->save();
            } catch (AuthorizationException $e) {
                // User doesn't have update permission, just continue without saving
            }
        }
        $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
        // Convert service names with dots and dashes to use underscores for HTML form binding
        $sanitizedDomains = [];
        foreach ($this->parsedServiceDomains as $serviceName => $domain) {
            $sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
            $sanitizedDomains[$sanitizedKey] = $domain;
        }
        $this->parsedServiceDomains = $sanitizedDomains;

        $this->customLabels = $this->application->parseContainerLabels();
        if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) {
            // Only update custom labels if user has permission
            try {
                $this->authorize('update', $this->application);
                $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
                $this->application->custom_labels = base64_encode($this->customLabels);
                $this->application->save();
            } catch (AuthorizationException $e) {
                // User doesn't have update permission, just use existing labels
                // $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
            }
        }
        $this->initialDockerComposeLocation = $this->application->docker_compose_location;
        if ($this->application->build_pack === 'dockercompose' && ! $this->application->docker_compose_raw) {
            // Only load compose file if user has update permission
            try {
                $this->authorize('update', $this->application);
                $this->initLoadingCompose = true;
                $this->dispatch('info', 'Loading docker compose file.');
            } catch (AuthorizationException $e) {
                // User doesn't have update permission, skip loading compose file
            }
        }

        if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
            $this->dispatch('configurationChanged');
        }

        // Sync data from model to properties at the END, after all business logic
        // This ensures any modifications to $this->application during mount() are reflected in properties
        $this->syncData();
    }

    public function syncData(bool $toModel = false): void
    {
        if ($toModel) {
            $this->validate();

            // Application properties
            $this->application->name = $this->name;
            $this->application->description = $this->description;
            $this->application->fqdn = $this->fqdn;
            $this->application->git_repository = $this->gitRepository;
            $this->application->git_branch = $this->gitBranch;
            $this->application->git_commit_sha = $this->gitCommitSha;
            $this->application->install_command = $this->installCommand;
            $this->application->build_command = $this->buildCommand;
            $this->application->start_command = $this->startCommand;
            $this->application->build_pack = $this->buildPack;
            $this->application->static_image = $this->staticImage;
            $this->application->base_directory = $this->baseDirectory;
            $this->application->publish_directory = $this->publishDirectory;
            $this->application->ports_exposes = $this->portsExposes;
            $this->application->ports_mappings = $this->portsMappings;
            $this->application->custom_network_aliases = $this->customNetworkAliases;
            $this->application->dockerfile = $this->dockerfile;
            $this->application->dockerfile_location = $this->dockerfileLocation;
            $this->application->dockerfile_target_build = $this->dockerfileTargetBuild;
            $this->application->docker_registry_image_name = $this->dockerRegistryImageName;
            $this->application->docker_registry_image_tag = $this->dockerRegistryImageTag;
            $this->application->docker_compose_location = $this->dockerComposeLocation;
            $this->application->docker_compose = $this->dockerCompose;
            $this->application->docker_compose_raw = $this->dockerComposeRaw;
            $this->application->docker_compose_custom_start_command = $this->dockerComposeCustomStartCommand;
            $this->application->docker_compose_custom_build_command = $this->dockerComposeCustomBuildCommand;
            $this->application->custom_labels = is_null($this->customLabels)
                ? null
                : base64_encode($this->customLabels);
            $this->application->custom_docker_run_options = $this->customDockerRunOptions;
            $this->application->pre_deployment_command = $this->preDeploymentCommand;
            $this->application->pre_deployment_command_container = $this->preDeploymentCommandContainer;
            $this->application->post_deployment_command = $this->postDeploymentCommand;
            $this->application->post_deployment_command_container = $this->postDeploymentCommandContainer;
            $this->application->custom_nginx_configuration = $this->customNginxConfiguration;
            $this->application->is_http_basic_auth_enabled = $this->isHttpBasicAuthEnabled;
            $this->application->http_basic_auth_username = $this->httpBasicAuthUsername;
            $this->application->http_basic_auth_password = $this->httpBasicAuthPassword;
            $this->application->watch_paths = $this->watchPaths;
            $this->application->redirect = $this->redirect;

            // Application settings properties
            $this->application->settings->is_static = $this->isStatic;
            $this->application->settings->is_spa = $this->isSpa;
            $this->application->settings->is_build_server_enabled = $this->isBuildServerEnabled;
            $this->application->settings->is_preserve_repository_enabled = $this->isPreserveRepositoryEnabled;
            $this->application->settings->is_container_label_escape_enabled = $this->isContainerLabelEscapeEnabled;
            $this->application->settings->is_container_label_readonly_enabled = $this->isContainerLabelReadonlyEnabled;

            $this->application->settings->save();
        } else {
            // From model to properties
            $this->name = $this->application->name;
            $this->description = $this->application->description;
            $this->fqdn = $this->application->fqdn;
            $this->gitRepository = $this->application->git_repository;
            $this->gitBranch = $this->application->git_branch;
            $this->gitCommitSha = $this->application->git_commit_sha;
            $this->installCommand = $this->application->install_command;
            $this->buildCommand = $this->application->build_command;
            $this->startCommand = $this->application->start_command;
            $this->buildPack = $this->application->build_pack;
            $this->staticImage = $this->application->static_image;
            $this->baseDirectory = $this->application->base_directory;
            $this->publishDirectory = $this->application->publish_directory;
            $this->portsExposes = $this->application->ports_exposes;
            $this->portsMappings = $this->application->ports_mappings;
            $this->customNetworkAliases = $this->application->custom_network_aliases;
            $this->dockerfile = $this->application->dockerfile;
            $this->dockerfileLocation = $this->application->dockerfile_location;
            $this->dockerfileTargetBuild = $this->application->dockerfile_target_build;
            $this->dockerRegistryImageName = $this->application->docker_registry_image_name;
            $this->dockerRegistryImageTag = $this->application->docker_registry_image_tag;
            $this->dockerComposeLocation = $this->application->docker_compose_location;
            $this->dockerCompose = $this->application->docker_compose;
            $this->dockerComposeRaw = $this->application->docker_compose_raw;
            $this->dockerComposeCustomStartCommand = $this->application->docker_compose_custom_start_command;
            $this->dockerComposeCustomBuildCommand = $this->application->docker_compose_custom_build_command;
            $this->customLabels = $this->application->parseContainerLabels();
            $this->customDockerRunOptions = $this->application->custom_docker_run_options;
            $this->preDeploymentCommand = $this->application->pre_deployment_command;
            $this->preDeploymentCommandContainer = $this->application->pre_deployment_command_container;
            $this->postDeploymentCommand = $this->application->post_deployment_command;
            $this->postDeploymentCommandContainer = $this->application->post_deployment_command_container;
            $this->customNginxConfiguration = $this->application->custom_nginx_configuration;
            $this->isHttpBasicAuthEnabled = $this->application->is_http_basic_auth_enabled;
            $this->httpBasicAuthUsername = $this->application->http_basic_auth_username;
            $this->httpBasicAuthPassword = $this->application->http_basic_auth_password;
            $this->watchPaths = $this->application->watch_paths;
            $this->redirect = $this->application->redirect;

            // Application settings properties
            $this->isStatic = $this->application->settings->is_static;
            $this->isSpa = $this->application->settings->is_spa;
            $this->isBuildServerEnabled = $this->application->settings->is_build_server_enabled;
            $this->isPreserveRepositoryEnabled = $this->application->settings->is_preserve_repository_enabled;
            $this->isContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
            $this->isContainerLabelReadonlyEnabled = $this->application->settings->is_container_label_readonly_enabled;
        }
    }

    public function instantSave()
    {
        try {
            $this->authorize('update', $this->application);

            $oldPortsExposes = $this->application->ports_exposes;
            $oldIsContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
            $oldIsPreserveRepositoryEnabled = $this->application->settings->is_preserve_repository_enabled;
            $oldIsSpa = $this->application->settings->is_spa;
            $oldIsHttpBasicAuthEnabled = $this->application->is_http_basic_auth_enabled;

            $this->syncData(toModel: true);

            if ($oldIsSpa !== $this->isSpa) {
                $this->generateNginxConfiguration($this->isSpa ? 'spa' : 'static');
            }
            if ($oldIsHttpBasicAuthEnabled !== $this->isHttpBasicAuthEnabled) {
                $this->application->save();
            }

            $this->dispatch('success', 'Settings saved.');
            $this->application->refresh();

            $this->syncData();

            // If port_exposes changed, reset default labels
            if ($oldPortsExposes !== $this->portsExposes || $oldIsContainerLabelEscapeEnabled !== $this->isContainerLabelEscapeEnabled) {
                $this->resetDefaultLabels(false);
            }
            if ($oldIsPreserveRepositoryEnabled !== $this->isPreserveRepositoryEnabled) {
                if ($this->isPreserveRepositoryEnabled === false) {
                    $this->application->fileStorages->each(function ($storage) {
                        $storage->is_based_on_git = $this->isPreserveRepositoryEnabled;
                        $storage->save();
                    });
                }
            }
            if ($this->isContainerLabelReadonlyEnabled) {
                $this->resetDefaultLabels(false);
            }
        } catch (\Throwable $e) {
            return handleError($e, $this);
        }
    }

    public function loadComposeFile($isInit = false, $showToast = true, ?string $restoreBaseDirectory = null, ?string $restoreDockerComposeLocation = null)
    {
        try {
            $this->authorize('update', $this->application);

            if ($isInit && $this->application->docker_compose_raw) {
                return;
            }

            ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit, $restoreBaseDirectory, $restoreDockerComposeLocation);
            if (is_null($this->parsedServices)) {
                $showToast && $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');

                return;
            }

            // Refresh parsedServiceDomains to reflect any changes in docker_compose_domains
            $this->application->refresh();

            // Sync the docker_compose_raw from the model to the component property
            // This ensures the Monaco editor displays the loaded compose file
            $this->syncData();

            $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
            // Convert service names with dots and dashes to use underscores for HTML form binding
            $sanitizedDomains = [];
            foreach ($this->parsedServiceDomains as $serviceName => $domain) {
                $sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
                $sanitizedDomains[$sanitizedKey] = $domain;
            }
            $this->parsedServiceDomains = $sanitizedDomains;

            $showToast && $this->dispatch('success', 'Docker compose file loaded.');
            $this->dispatch('compose_loaded');
            $this->dispatch('refreshStorages');
            $this->dispatch('refreshEnvs');
        } catch (\Throwable $e) {
            // Refresh model to get restored values from Application::loadComposeFile
            $this->application->refresh();
            // Sync restored values back to component properties for UI update

            $this->syncData();

            return handleError($e, $this);
        } finally {
            $this->initLoadingCompose = false;
        }
    }

    public function generateDomain(string $serviceName)
    {
        try {
            $this->authorize('update', $this->application);

            $uuid = new_public_id();
            $domain = generateUrl(server: $this->application->destination->server, random: $uuid);
            $sanitizedKey = str($serviceName)->replace('-', '_')->replace('.', '_')->toString();
            $this->parsedServiceDomains[$sanitizedKey]['domain'] = $domain;

            // Convert back to original service names for storage
            $originalDomains = [];
            foreach ($this->parsedServiceDomains as $key => $value) {
                // Find the original service name by checking parsed services
                $originalServiceName = $key;
                if (isset($this->parsedServices['services'])) {
                    foreach ($this->parsedServices['services'] as $originalName => $service) {
                        if (str($originalName)->replace('-', '_')->replace('.', '_')->toString() === $key) {
                            $originalServiceName = $originalName;
                            break;
                        }
                    }
                }
                $originalDomains[$originalServiceName] = $value;
            }

            $this->application->docker_compose_domains = json_encode($originalDomains);
            $this->application->save();
            $this->dispatch('success', 'Domain generated.');
            if ($this->application->build_pack === 'dockercompose') {
                $this->loadComposeFile(showToast: false);
            }

            return $domain;
        } catch (\Throwable $e) {
            return handleError($e, $this);
        }
    }

    public function updatedIsStatic($value)
    {
        if ($value) {
            $this->generateNginxConfiguration();
        }
    }

    public function updatedBuildPack()
    {
        // Check if user has permission to update
        try {
            $this->authorize('update', $this->application);
        } catch (AuthorizationException $e) {
            // User doesn't have permission, revert the change and return
            $this->application->refresh();
            $this->syncData();

            return;
        }

        // Sync property to model before checking/modifying
        $this->syncData(toModel: true);

        if ($this->buildPack !== 'nixpacks' && $this->buildPack !== 'railpack') {
            $this->isStatic = false;
            $this->application->settings->is_static = false;
            $this->application->settings->save();
        } else {
            $this->resetDefaultLabels(false);
        }
        if ($this->buildPack === 'dockercompose') {
            // Only update if user has permission
            try {
                $this->authorize('update', $this->application);
                $this->fqdn = null;
                $this->application->fqdn = null;
                $this->application->settings->save();
            } catch (AuthorizationException $e) {
                // User doesn't have update permission, just continue without saving
            }
        }
        if ($this->buildPack === 'static') {
            $this->portsExposes = '80';
            $this->application->ports_exposes = '80';
            $this->resetDefaultLabels(false);
            $this->generateNginxConfiguration();
        }
        $this->submit();
        $this->dispatch('buildPackUpdated');
    }

    public function getWildcardDomain()
    {
        try {
            $this->authorize('update', $this->application);

            $server = data_get($this->application, 'destination.server');
            if ($server) {
                $fqdn = generateUrl(server: $server, random: $this->application->uuid);
                $this->fqdn = $fqdn;
                $this->syncData(toModel: true);
                $this->application->save();
                $this->application->refresh();
                $this->syncData();
                $this->resetDefaultLabels();
                $this->dispatch('success', 'Wildcard domain generated.');
            }
        } catch (\Throwable $e) {
            return handleError($e, $this);
        }
    }

    public function generateNginxConfiguration($type = 'static')
    {
        try {
            $this->authorize('update', $this->application);

            $this->customNginxConfiguration = defaultNginxConfiguration($type);
            $this->syncData(toModel: true);
            $this->application->save();
            $this->application->refresh();
            $this->syncData();
            $this->dispatch('success', 'Nginx configuration generated.');
        } catch (\Throwable $e) {
            return handleError($e, $this);
        }
    }

    public function resetDefaultLabels($manualReset = false)
    {
        try {
            if (! $this->isContainerLabelReadonlyEnabled && ! $manualReset) {
                return;
            }
            $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
            $this->application->custom_labels = base64_encode($this->customLabels);
            $this->application->save();
            $this->application->refresh();
            $this->syncData();
            if ($this->buildPack === 'dockercompose') {
                $this->loadComposeFile(showToast: false);
            }
            $this->dispatch('configurationChanged');
        } catch (\Throwable $e) {
            return handleError($e, $this);
        }
    }

    public function checkFqdns($showToaster = true)
    {
        if ($this->fqdn) {
            $domains = str($this->fqdn)->trim()->explode(',');
            if ($this->application->additional_servers->count() === 0) {
                foreach ($domains as $domain) {
                    if (! validateDNSEntry($domain, $this->application->destination->server)) {
                        $showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
                    }
                }
            }

            // Check for domain conflicts if not forcing save
            if (! $this->forceSaveDomains) {
                $result = checkDomainUsage(resource: $this->application);
                if ($result['hasConflicts']) {
                    $this->domainConflicts = $result['conflicts'];
                    $this->showDomainConflictModal = true;

                    return false;
                }
            } else {
                // Reset the force flag after using it
                $this->forceSaveDomains = false;
            }

            $this->fqdn = $domains->implode(',');
            $this->application->fqdn = $this->fqdn;
            $this->resetDefaultLabels(false);
        }

        return true;
    }

    public function confirmDomainUsage()
    {
        $this->forceSaveDomains = true;
        $this->showDomainConflictModal = false;
        $this->submit();
    }

    public function setRedirect()
    {
        $this->authorize('update', $this->application);

        try {
            $this->application->redirect = $this->redirect;
            $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
            if ($has_www === 0 && $this->application->redirect === 'www') {
                $this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');

                return;
            }
            $this->application->save();
            $this->resetDefaultLabels();
            $this->dispatch('success', 'Redirect updated.');
        } catch (\Throwable $e) {
            return handleError($e, $this);
        }
    }

    public function submit($showToaster = true)
    {
        try {
            $this->authorize('update', $this->application);

            $this->resetErrorBag();

            $this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString() ?: null;
            if ($this->portsMappings) {
                $this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
            }

            $this->validate();

            $oldPortsExposes = $this->application->ports_exposes;
            $oldIsContainerLabelEscapeEnabled = $this->application->settings->is_container_label_escape_enabled;
            $oldDockerComposeLocation = $this->initialDockerComposeLocation;
            $oldBaseDirectory = $this->application->base_directory;

            // Process FQDN with intermediate variable to avoid Collection/string confusion
            $this->fqdn = ValidationPatterns::normalizeApplicationDomains($this->fqdn);
            $warning = sslipDomainWarning($this->fqdn);
            if ($warning) {
                $this->dispatch('warning', __('warning.sslipdomain'));
            }

            $this->syncData(toModel: true);

            if ($this->application->isDirty('redirect')) {
                $this->setRedirect();
            }
            if ($this->application->isDirty('dockerfile')) {
                $this->application->parseHealthcheckFromDockerfile($this->application->dockerfile);
            }

            if (! $this->checkFqdns()) {
                return; // Stop if there are conflicts and user hasn't confirmed
            }

            // Normalize paths BEFORE validation
            if ($this->baseDirectory && $this->baseDirectory !== '/') {
                $this->baseDirectory = rtrim($this->baseDirectory, '/');
                $this->application->base_directory = $this->baseDirectory;
            }
            if ($this->publishDirectory && $this->publishDirectory !== '/') {
                $this->publishDirectory = rtrim($this->publishDirectory, '/');
                $this->application->publish_directory = $this->publishDirectory;
            }

            // Validate docker compose file path BEFORE saving to database
            // This prevents invalid paths from being persisted when validation fails
            if ($this->buildPack === 'dockercompose' &&
                ($oldDockerComposeLocation !== $this->dockerComposeLocation ||
                 $oldBaseDirectory !== $this->baseDirectory)) {
                // Pass original values to loadComposeFile so it can restore them on failure
                // The finally block in Application::loadComposeFile will save these original
                // values if validation fails, preventing invalid paths from being persisted
                $compose_return = $this->loadComposeFile(
                    isInit: false,
                    showToast: false,
                    restoreBaseDirectory: $oldBaseDirectory,
                    restoreDockerComposeLocation: $oldDockerComposeLocation
                );
                if ($compose_return instanceof Event) {
                    // Validation failed - restore original values to component properties
                    $this->baseDirectory = $oldBaseDirectory;
                    $this->dockerComposeLocation = $oldDockerComposeLocation;
                    // The model was saved by loadComposeFile's finally block with original values
                    // Refresh to sync component with database state
                    $this->application->refresh();

                    return;
                }
            }

            $this->application->save();
            if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
                $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
                $this->application->custom_labels = base64_encode($this->customLabels);
                $this->application->save();
            }

            if ($oldPortsExposes !== $this->portsExposes || $oldIsContainerLabelEscapeEnabled !== $this->isContainerLabelEscapeEnabled) {
                $this->resetDefaultLabels();
            }
            if ($this->buildPack === 'dockerimage') {
                $this->validate([
                    'dockerRegistryImageName' => ValidationPatterns::dockerImageNameRules(required: true),
                ]);
            }

            if ($this->customDockerRunOptions) {
                $this->customDockerRunOptions = str($this->customDockerRunOptions)->trim()->toString();
                $this->application->custom_docker_run_options = $this->customDockerRunOptions;
            }
            if ($this->dockerfile) {
                $port = get_port_from_dockerfile($this->dockerfile);
                if ($port && ! $this->portsExposes) {
                    $this->portsExposes = $port;
                    $this->application->ports_exposes = $port;
                }
            }
            if ($this->buildPack === 'dockercompose') {
                foreach ($this->parsedServiceDomains as $serviceName => $service) {
                    $this->parsedServiceDomains[$serviceName]['domain'] = ValidationPatterns::normalizeApplicationDomains(data_get($service, 'domain'));
                }
                $this->application->docker_compose_domains = json_encode($this->parsedServiceDomains);
                if ($this->application->isDirty('docker_compose_domains')) {
                    foreach ($this->parsedServiceDomains as $service) {
                        $domain = data_get($service, 'domain');
                        if ($domain) {
                            if (! validateDNSEntry($domain, $this->application->destination->server)) {
                                $showToaster && $this->dispatch('error', 'Validating DNS failed.', "Make sure you have added the DNS records correctly.<br><br>$domain->{$this->application->destination->server->ip}<br><br>Check this <a target='_blank' class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/dns-configuration'>documentation</a> for further help.");
                            }
                        }
                    }
                    // Check for domain conflicts if not forcing save
                    if (! $this->forceSaveDomains) {
                        $result = checkDomainUsage(resource: $this->application);
                        if ($result['hasConflicts']) {
                            $this->domainConflicts = $result['conflicts'];
                            $this->showDomainConflictModal = true;

                            return;
                        }
                    } else {
                        // Reset the force flag after using it
                        $this->forceSaveDomains = false;
                    }

                    $this->application->save();
                    $this->resetDefaultLabels();
                }
            }
            $this->application->custom_labels = base64_encode($this->customLabels);
            $this->application->save();
            $this->application->refresh();
            $this->syncData();
            $showToaster && ! $warning && $this->dispatch('success', 'Application settings updated!');
        } catch (\Throwable $e) {
            $this->application->refresh();
            $this->syncData();

            return handleError($e, $this);
        } finally {
            $this->dispatch('configurationChanged');
        }
    }

    public function downloadConfig()
    {
        $config = GenerateConfig::run($this->application, true);
        $fileName = str($this->application->name)->slug()->append('_config.json');

        return response()->streamDownload(function () use ($config) {
            echo $config;
        }, $fileName, [
            'Content-Type' => 'application/json',
            'Content-Disposition' => 'attachment; filename='.$fileName,
        ]);
    }

    public function getDetectedPortInfoProperty(): ?array
    {
        $detectedPort = $this->application->detectPortFromEnvironment();

        if (! $detectedPort) {
            return null;
        }

        $portsExposesArray = $this->application->ports_exposes_array;
        $isMatch = in_array($detectedPort, $portsExposesArray);
        $isEmpty = empty($portsExposesArray);

        return [
            'port' => $detectedPort,
            'matches' => $isMatch,
            'isEmpty' => $isEmpty,
        ];
    }

    public function getDockerComposeBuildCommandPreviewProperty(): string
    {
        if (! $this->dockerComposeCustomBuildCommand) {
            return '';
        }

        // Normalize baseDirectory to prevent double slashes (e.g., when baseDirectory is '/')
        $normalizedBase = $this->baseDirectory === '/' ? '' : rtrim($this->baseDirectory, '/');

        // Use relative path for clarity in preview (e.g., ./backend/docker-compose.yaml)
        // Actual deployment uses absolute path: /artifacts/{deployment_uuid}{base_directory}{docker_compose_location}
        // Build-time env path references ApplicationDeploymentJob::BUILD_TIME_ENV_PATH as source of truth
        $command = injectDockerComposeFlags(
            $this->dockerComposeCustomBuildCommand,
            ".{$normalizedBase}{$this->dockerComposeLocation}",
            ApplicationDeploymentJob::BUILD_TIME_ENV_PATH
        );

        // Inject build args if not using build secrets
        if (! $this->application->settings->use_build_secrets) {
            $buildTimeEnvs = $this->application->environment_variables()
                ->where('is_buildtime', true)
                ->get();

            if ($buildTimeEnvs->isNotEmpty()) {
                $buildArgs = generateDockerBuildArgs($buildTimeEnvs);
                $buildArgsString = $buildArgs->implode(' ');

                $command = injectDockerComposeBuildArgs($command, $buildArgsString);
            }
        }

        return $command;
    }

    public function getDockerComposeStartCommandPreviewProperty(): string
    {
        if (! $this->dockerComposeCustomStartCommand) {
            return '';
        }

        // Normalize baseDirectory to prevent double slashes (e.g., when baseDirectory is '/')
        $normalizedBase = $this->baseDirectory === '/' ? '' : rtrim($this->baseDirectory, '/');

        // Use relative path for clarity in preview (e.g., ./backend/docker-compose.yaml)
        // Placeholder {workdir}/.env shows it's the workdir .env file (runtime env, not build-time)
        return injectDockerComposeFlags(
            $this->dockerComposeCustomStartCommand,
            ".{$normalizedBase}{$this->dockerComposeLocation}",
            '{workdir}/.env'
        );
    }
}
