NewSandboxHintFileWizardPage.java

/*******************************************************************************
 * Copyright (c) 2026 Carsten Hammer.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     Carsten Hammer - initial API and implementation
 *******************************************************************************/
package org.sandbox.jdt.internal.ui.wizard;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.dialogs.ContainerSelectionDialog;

/**
 * First wizard page for the New Sandbox Hint File wizard.
 *
 * <p>Lets the user choose the target container (project/folder), file name,
 * metadata (ID, description, severity, min Java version, tags), and a
 * template.</p>
 *
 * @since 1.5.0
 */
public class NewSandboxHintFileWizardPage extends WizardPage {

	private static final String FILE_EXTENSION = ".sandbox-hint"; //$NON-NLS-1$

	private Text containerText;
	private Text fileText;
	private Text idText;
	private Text descriptionText;
	private Combo severityCombo;
	private Combo minJavaCombo;
	private Text tagsText;
	private SandboxHintTemplates selectedTemplate = SandboxHintTemplates.SIMPLE;

	private final IStructuredSelection selection;

	/**
	 * Creates the wizard page.
	 *
	 * @param selection the current workbench selection
	 */
	protected NewSandboxHintFileWizardPage(IStructuredSelection selection) {
		super("newSandboxHintFilePage"); //$NON-NLS-1$
		setTitle("New Sandbox Hint File"); //$NON-NLS-1$
		setDescription("Create a new .sandbox-hint file with transformation rules"); //$NON-NLS-1$
		this.selection = selection;
	}

	@Override
	public void createControl(Composite parent) {
		Composite container = new Composite(parent, SWT.NONE);
		GridLayout layout = new GridLayout(3, false);
		container.setLayout(layout);

		ModifyListener modifyListener = e -> dialogChanged();

		// --- Container selection ---
		Label containerLabel = new Label(container, SWT.NONE);
		containerLabel.setText("&Container:"); //$NON-NLS-1$

		containerText = new Text(container, SWT.BORDER | SWT.SINGLE);
		containerText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		containerText.addModifyListener(modifyListener);

		Button browseButton = new Button(container, SWT.PUSH);
		browseButton.setText("&Browse..."); //$NON-NLS-1$
		browseButton.addSelectionListener(new SelectionAdapter() {
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleBrowse();
			}
		});

		// --- File name ---
		Label fileLabel = new Label(container, SWT.NONE);
		fileLabel.setText("&File name:"); //$NON-NLS-1$

		fileText = new Text(container, SWT.BORDER | SWT.SINGLE);
		fileText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		fileText.addModifyListener(modifyListener);
		new Label(container, SWT.NONE); // spacer

		// --- Metadata group ---
		Group metadataGroup = new Group(container, SWT.NONE);
		metadataGroup.setText("Metadata"); //$NON-NLS-1$
		metadataGroup.setLayout(new GridLayout(2, false));
		GridData groupData = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
		metadataGroup.setLayoutData(groupData);

		new Label(metadataGroup, SWT.NONE).setText("&ID:"); //$NON-NLS-1$
		idText = new Text(metadataGroup, SWT.BORDER | SWT.SINGLE);
		idText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		idText.addModifyListener(modifyListener);

		new Label(metadataGroup, SWT.NONE).setText("D&escription:"); //$NON-NLS-1$
		descriptionText = new Text(metadataGroup, SWT.BORDER | SWT.SINGLE);
		descriptionText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

		new Label(metadataGroup, SWT.NONE).setText("&Severity:"); //$NON-NLS-1$
		severityCombo = new Combo(metadataGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		severityCombo.setItems(new String[] { "info", "warning", "error", "hint" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		severityCombo.select(0);
		severityCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

		new Label(metadataGroup, SWT.NONE).setText("Min &Java:"); //$NON-NLS-1$
		minJavaCombo = new Combo(metadataGroup, SWT.DROP_DOWN | SWT.READ_ONLY);
		minJavaCombo.setItems(new String[] { "", "11", "17", "21" }); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
		minJavaCombo.select(0);
		minJavaCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));

		new Label(metadataGroup, SWT.NONE).setText("&Tags:"); //$NON-NLS-1$
		tagsText = new Text(metadataGroup, SWT.BORDER | SWT.SINGLE);
		tagsText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
		tagsText.setMessage("encoding, modernization"); //$NON-NLS-1$

		// --- Template group ---
		Group templateGroup = new Group(container, SWT.NONE);
		templateGroup.setText("Template"); //$NON-NLS-1$
		templateGroup.setLayout(new GridLayout(1, false));
		GridData tplData = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1);
		templateGroup.setLayoutData(tplData);

		for (SandboxHintTemplates tpl : SandboxHintTemplates.values()) {
			Button radio = new Button(templateGroup, SWT.RADIO);
			radio.setText(tpl.getLabel());
			radio.setData(tpl);
			if (tpl == SandboxHintTemplates.SIMPLE) {
				radio.setSelection(true);
			}
			radio.addSelectionListener(new SelectionAdapter() {
				@Override
				public void widgetSelected(SelectionEvent e) {
					if (((Button) e.widget).getSelection()) {
						selectedTemplate = (SandboxHintTemplates) ((Button) e.widget).getData();
					}
				}
			});
		}

		// --- Initialize from selection ---
		initializeFromSelection();

		dialogChanged();
		setControl(container);
	}

	/**
	 * Pre-fills the container path from the workbench selection and
	 * always sets a sensible default file name.
	 */
	private void initializeFromSelection() {
		// Always set a default file name
		fileText.setText("rules" + FILE_EXTENSION); //$NON-NLS-1$

		if (selection == null || selection.isEmpty()) {
			return;
		}
		Object element = selection.getFirstElement();
		if (element instanceof IResource resource) {
			IContainer container;
			if (resource instanceof IContainer c) {
				container = c;
			} else {
				container = resource.getParent();
			}
			containerText.setText(container.getFullPath().toString());
		}
	}

	private void handleBrowse() {
		ContainerSelectionDialog dialog = new ContainerSelectionDialog(
				getShell(), ResourcesPlugin.getWorkspace().getRoot(), false,
				"Select the target folder"); //$NON-NLS-1$
		if (dialog.open() == ContainerSelectionDialog.OK) {
			Object[] result = dialog.getResult();
			if (result.length == 1) {
				containerText.setText(((IPath) result[0]).toString());
			}
		}
	}

	private void dialogChanged() {
		String container = containerText.getText();
		String file = fileText.getText();

		if (container.isEmpty()) {
			updateStatus("Container must be specified"); //$NON-NLS-1$
			return;
		}
		IResource resource = ResourcesPlugin.getWorkspace().getRoot()
				.findMember(new Path(container));
		if (resource == null || !(resource instanceof IContainer) || !resource.isAccessible()) {
			updateStatus("Container must exist and be accessible"); //$NON-NLS-1$
			return;
		}
		if (file.isEmpty()) {
			updateStatus("File name must be specified"); //$NON-NLS-1$
			return;
		}
		if (!file.endsWith(FILE_EXTENSION)) {
			updateStatus("File name must end with " + FILE_EXTENSION); //$NON-NLS-1$
			return;
		}
		if (file.replace('\\', '/').indexOf('/') >= 0) {
			updateStatus("File name must not contain path separators"); //$NON-NLS-1$
			return;
		}
		updateStatus(null);
	}

	private void updateStatus(String message) {
		setErrorMessage(message);
		setPageComplete(message == null);
	}

	// --- Public accessors for the wizard ---

	/** Returns the container path entered by the user. */
	public String getContainerPath() {
		return containerText.getText();
	}

	/** Returns the file name entered by the user. */
	public String getFileName() {
		return fileText.getText();
	}

	/** Returns the hint ID entered by the user (may be empty). */
	public String getHintId() {
		String text = idText.getText().trim();
		return text.isEmpty() ? null : text;
	}

	/** Returns the description entered by the user (may be empty). */
	public String getHintDescription() {
		String text = descriptionText.getText().trim();
		return text.isEmpty() ? null : text;
	}

	/** Returns the selected severity string. */
	public String getSeverityValue() {
		return severityCombo.getText();
	}

	/** Returns the selected minimum Java version, or 0 if none. */
	public int getMinJavaVersion() {
		String text = minJavaCombo.getText().trim();
		if (text.isEmpty()) {
			return 0;
		}
		try {
			return Integer.parseInt(text);
		} catch (NumberFormatException e) {
			return 0;
		}
	}

	/** Returns the raw tags text (comma-separated). */
	public String getTagsText() {
		return tagsText.getText().trim();
	}

	/** Returns the template selected by the user. */
	public SandboxHintTemplates getSelectedTemplate() {
		return selectedTemplate;
	}
}