using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Windows.Forms;

[assembly: AssemblyTitle("PANDA")]
[assembly: AssemblyDescription("Pseudonymisierung alphanumerischer Nutzdaten durch Alphabetverschiebung")]
[assembly: AssemblyProduct("PANDA")]
[assembly: AssemblyCompany("PANDA")]
[assembly: AssemblyVersion("1.4.0.0")]
[assembly: AssemblyFileVersion("1.4.0.0")]

namespace Panda
{
    internal static class Program
    {
        [STAThread]
        private static void Main(string[] args)
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            if (args.Length == 2 && string.Equals(args[0], "--screenshot", StringComparison.OrdinalIgnoreCase))
            {
                using (var form = new MainForm())
                {
                    form.Size = new Size(1320, 820);
                    form.Show();
                    form.LoadPreviewData();
                    Application.DoEvents();
                    using (var bitmap = new Bitmap(form.Width, form.Height))
                    {
                        form.DrawToBitmap(bitmap, new Rectangle(Point.Empty, form.Size));
                        bitmap.Save(args[1], System.Drawing.Imaging.ImageFormat.Png);
                    }
                    form.Close();
                }
                return;
            }
            if (args.Length == 3 && string.Equals(args[0], "--wizard-screenshot", StringComparison.OrdinalIgnoreCase))
            {
                using (var wizard = new ImportWizardForm(args[1]))
                {
                    wizard.Show();
                    Application.DoEvents();
                    wizard.UncheckLastColumnForPreview();
                    Application.DoEvents();
                    using (var bitmap = new Bitmap(wizard.Width, wizard.Height))
                    {
                        wizard.DrawToBitmap(bitmap, new Rectangle(Point.Empty, wizard.Size));
                        bitmap.Save(args[2], System.Drawing.Imaging.ImageFormat.Png);
                    }
                    wizard.Close();
                }
                return;
            }
            if (args.Length == 2 && string.Equals(args[0], "--settings-screenshot", StringComparison.OrdinalIgnoreCase))
            {
                var previewSettings = new AppSettings { DefaultShift = 6, ConfirmBeforeShift = true };
                using (var settingsForm = new SettingsForm(previewSettings))
                {
                    settingsForm.Show();
                    Application.DoEvents();
                    using (var bitmap = new Bitmap(settingsForm.Width, settingsForm.Height))
                    {
                        settingsForm.DrawToBitmap(bitmap, new Rectangle(Point.Empty, settingsForm.Size));
                        bitmap.Save(args[1], System.Drawing.Imaging.ImageFormat.Png);
                    }
                    settingsForm.Close();
                }
                return;
            }
            Application.Run(new MainForm());
        }
    }

    internal sealed class CsvDocument
    {
        public List<string> Headers = new List<string>();
        public List<List<string>> Rows = new List<List<string>>();
        public char Delimiter;
        public bool FirstRowIsHeader;
    }

    internal static class CsvCodec
    {
        public static CsvDocument Load(string path, bool firstRowIsHeader)
        {
            string text;
            using (var reader = new StreamReader(path, Encoding.UTF8, true))
                text = reader.ReadToEnd();

            char delimiter = DetectDelimiter(text);
            var records = Parse(text, delimiter);
            var document = new CsvDocument
            {
                Delimiter = delimiter,
                FirstRowIsHeader = firstRowIsHeader
            };

            int columnCount = records.Count == 0 ? 0 : records.Max(row => row.Count);
            if (firstRowIsHeader && records.Count > 0)
            {
                for (int column = 0; column < columnCount; column++)
                {
                    string value = column < records[0].Count ? records[0][column] : string.Empty;
                    document.Headers.Add(string.IsNullOrWhiteSpace(value) ? "Spalte " + (column + 1) : value);
                }
                records.RemoveAt(0);
            }
            else
            {
                for (int column = 0; column < columnCount; column++)
                    document.Headers.Add("Spalte " + (column + 1));
            }

            foreach (var record in records)
            {
                while (record.Count < columnCount)
                    record.Add(string.Empty);
                document.Rows.Add(record);
            }

            return document;
        }

        public static void Save(string path, CsvDocument document, IList<IList<string>> rows)
        {
            using (var writer = new StreamWriter(path, false, new UTF8Encoding(true)))
            {
                if (document.FirstRowIsHeader)
                    WriteRecord(writer, document.Headers, document.Delimiter);

                foreach (var row in rows)
                    WriteRecord(writer, row, document.Delimiter);
            }
        }

        public static CsvDocument SelectColumns(CsvDocument source, IList<int> selectedColumns)
        {
            var result = new CsvDocument
            {
                Delimiter = source.Delimiter,
                FirstRowIsHeader = source.FirstRowIsHeader
            };

            foreach (int column in selectedColumns)
            {
                if (column < 0 || column >= source.Headers.Count)
                    throw new ArgumentOutOfRangeException("selectedColumns");
                result.Headers.Add(source.Headers[column]);
            }

            foreach (var sourceRow in source.Rows)
            {
                var row = new List<string>();
                foreach (int column in selectedColumns)
                    row.Add(column < sourceRow.Count ? sourceRow[column] : string.Empty);
                result.Rows.Add(row);
            }
            return result;
        }

        private static void WriteRecord(TextWriter writer, IEnumerable<string> values, char delimiter)
        {
            writer.WriteLine(string.Join(delimiter.ToString(), values.Select(value => Escape(value ?? string.Empty, delimiter))));
        }

        private static string Escape(string value, char delimiter)
        {
            if (value.IndexOfAny(new[] { delimiter, '"', '\r', '\n' }) < 0)
                return value;
            return "\"" + value.Replace("\"", "\"\"") + "\"";
        }

        internal static char DetectDelimiter(string text)
        {
            string firstRecord = GetFirstLogicalRecord(text);
            char[] candidates = { ';', ',', '\t' };
            char best = ';';
            int bestCount = -1;
            foreach (char candidate in candidates)
            {
                int count = CountOutsideQuotes(firstRecord, candidate);
                if (count > bestCount)
                {
                    bestCount = count;
                    best = candidate;
                }
            }
            return best;
        }

        private static string GetFirstLogicalRecord(string text)
        {
            var builder = new StringBuilder();
            bool quoted = false;
            for (int index = 0; index < text.Length; index++)
            {
                char current = text[index];
                if (current == '"')
                {
                    if (quoted && index + 1 < text.Length && text[index + 1] == '"')
                    {
                        builder.Append("\"\"");
                        index++;
                        continue;
                    }
                    quoted = !quoted;
                }
                if (!quoted && (current == '\r' || current == '\n'))
                    break;
                builder.Append(current);
            }
            return builder.ToString();
        }

        private static int CountOutsideQuotes(string text, char delimiter)
        {
            bool quoted = false;
            int count = 0;
            for (int index = 0; index < text.Length; index++)
            {
                if (text[index] == '"')
                {
                    if (quoted && index + 1 < text.Length && text[index + 1] == '"')
                    {
                        index++;
                        continue;
                    }
                    quoted = !quoted;
                }
                else if (!quoted && text[index] == delimiter)
                {
                    count++;
                }
            }
            return count;
        }

        internal static List<List<string>> Parse(string text, char delimiter)
        {
            var rows = new List<List<string>>();
            var row = new List<string>();
            var field = new StringBuilder();
            bool quoted = false;

            for (int index = 0; index < text.Length; index++)
            {
                char current = text[index];
                if (quoted)
                {
                    if (current == '"')
                    {
                        if (index + 1 < text.Length && text[index + 1] == '"')
                        {
                            field.Append('"');
                            index++;
                        }
                        else
                        {
                            quoted = false;
                        }
                    }
                    else
                    {
                        field.Append(current);
                    }
                }
                else if (current == '"' && field.Length == 0)
                {
                    quoted = true;
                }
                else if (current == delimiter)
                {
                    row.Add(field.ToString());
                    field.Clear();
                }
                else if (current == '\r' || current == '\n')
                {
                    if (current == '\r' && index + 1 < text.Length && text[index + 1] == '\n')
                        index++;
                    row.Add(field.ToString());
                    field.Clear();
                    rows.Add(row);
                    row = new List<string>();
                }
                else
                {
                    field.Append(current);
                }
            }

            if (field.Length > 0 || row.Count > 0)
            {
                row.Add(field.ToString());
                rows.Add(row);
            }
            return rows;
        }
    }

    internal static class LetterShifter
    {
        public static string Shift(string value, int amount)
        {
            if (string.IsNullOrEmpty(value) || amount == 0)
                return value;

            var result = new StringBuilder(value.Length);
            foreach (char character in value)
            {
                if (character >= 'A' && character <= 'Z')
                    result.Append(ShiftInRange(character, 'A', 26, amount));
                else if (character >= 'a' && character <= 'z')
                    result.Append(ShiftInRange(character, 'a', 26, amount));
                else
                    result.Append(character);
            }
            return result.ToString();
        }

        private static char ShiftInRange(char value, char start, int length, int amount)
        {
            int shifted = ((value - start + amount) % length + length) % length;
            return (char)(start + shifted);
        }
    }

    internal sealed class AppSettings
    {
        public int DefaultShift = 1;
        public bool ConfirmBeforeShift = true;

        private static string SettingsPath
        {
            get
            {
                return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PANDA", "settings.ini");
            }
        }

        public static AppSettings Load()
        {
            try
            {
                if (File.Exists(SettingsPath))
                    return Parse(File.ReadAllLines(SettingsPath));
            }
            catch
            {
            }
            return new AppSettings();
        }

        public void Save()
        {
            string directory = Path.GetDirectoryName(SettingsPath);
            if (!Directory.Exists(directory))
                Directory.CreateDirectory(directory);
            File.WriteAllLines(SettingsPath, Serialize(), new UTF8Encoding(false));
        }

        internal static AppSettings Parse(IEnumerable<string> lines)
        {
            var settings = new AppSettings();
            foreach (string line in lines)
            {
                if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith("#"))
                    continue;
                int separator = line.IndexOf('=');
                if (separator <= 0)
                    continue;
                string key = line.Substring(0, separator).Trim();
                string value = line.Substring(separator + 1).Trim();
                if (string.Equals(key, "DefaultShift", StringComparison.OrdinalIgnoreCase))
                {
                    int parsed;
                    if (int.TryParse(value, out parsed))
                        settings.DefaultShift = Math.Max(1, Math.Min(25, parsed));
                }
                else if (string.Equals(key, "ConfirmBeforeShift", StringComparison.OrdinalIgnoreCase))
                {
                    bool parsed;
                    if (bool.TryParse(value, out parsed))
                        settings.ConfirmBeforeShift = parsed;
                }
            }
            return settings;
        }

        internal string[] Serialize()
        {
            return new[]
            {
                "DefaultShift=" + DefaultShift,
                "ConfirmBeforeShift=" + ConfirmBeforeShift
            };
        }
    }

    internal sealed class SettingsForm : Form
    {
        private readonly Color Navy = Color.FromArgb(24, 38, 58);
        private readonly Color Blue = Color.FromArgb(41, 112, 255);
        private readonly Color Background = Color.FromArgb(244, 247, 251);
        private readonly Color Muted = Color.FromArgb(94, 108, 128);
        private readonly NumericUpDown defaultShiftNumeric = new NumericUpDown();
        private readonly CheckBox confirmationCheckBox = new CheckBox();

        public int DefaultShift { get { return (int)defaultShiftNumeric.Value; } }
        public bool ConfirmBeforeShift { get { return confirmationCheckBox.Checked; } }

        public SettingsForm(AppSettings settings)
        {
            Text = "PANDA – Einstellungen";
            StartPosition = FormStartPosition.CenterParent;
            FormBorderStyle = FormBorderStyle.FixedDialog;
            MaximizeBox = false;
            MinimizeBox = false;
            ClientSize = new Size(560, 330);
            BackColor = Background;
            Font = new Font("Segoe UI", 9F);
            Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application;
            BuildLayout(settings);
        }

        private void BuildLayout(AppSettings settings)
        {
            var root = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 1,
                RowCount = 3,
                Padding = new Padding(24, 20, 24, 18),
                BackColor = Background
            };
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 78));
            root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 58));
            Controls.Add(root);

            var heading = new Panel { Dock = DockStyle.Fill };
            heading.Controls.Add(new Label
            {
                Text = "Einstellungen",
                Font = new Font("Segoe UI Semibold", 18F),
                ForeColor = Navy,
                AutoSize = true,
                Location = new Point(0, 0)
            });
            heading.Controls.Add(new Label
            {
                Text = "Lege die Standardwerte für zukünftige Umwandlungen fest.",
                ForeColor = Muted,
                AutoSize = true,
                Location = new Point(2, 40)
            });
            root.Controls.Add(heading, 0, 0);

            var card = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 2,
                RowCount = 4,
                BackColor = Color.White,
                Padding = new Padding(18, 14, 18, 14),
                Margin = new Padding(0, 0, 0, 12)
            };
            card.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
            card.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 90));
            card.RowStyles.Add(new RowStyle(SizeType.Absolute, 28));
            card.RowStyles.Add(new RowStyle(SizeType.Absolute, 38));
            card.RowStyles.Add(new RowStyle(SizeType.Absolute, 16));
            card.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
            var shiftLabel = new Label
            {
                Text = "Standard-Zählwert",
                Font = new Font("Segoe UI Semibold", 10F),
                ForeColor = Navy,
                AutoSize = true,
                Anchor = AnchorStyles.Left
            };
            card.Controls.Add(shiftLabel, 0, 0);
            card.SetColumnSpan(shiftLabel, 2);
            card.Controls.Add(new Label
            {
                Text = "Dieser Wert wird beim Programmstart und nach dem Speichern voreingestellt.",
                ForeColor = Muted,
                AutoSize = true,
                Anchor = AnchorStyles.Left
            }, 0, 1);
            defaultShiftNumeric.Minimum = 1;
            defaultShiftNumeric.Maximum = 25;
            defaultShiftNumeric.Value = Math.Max(1, Math.Min(25, settings.DefaultShift));
            defaultShiftNumeric.TextAlign = HorizontalAlignment.Center;
            defaultShiftNumeric.Dock = DockStyle.Fill;
            defaultShiftNumeric.Margin = new Padding(8, 5, 0, 5);
            card.Controls.Add(defaultShiftNumeric, 1, 1);
            confirmationCheckBox.Text = "Vor jeder Umwandlung eine Bestätigung mit dem gewählten Zählwert anzeigen";
            confirmationCheckBox.Checked = settings.ConfirmBeforeShift;
            confirmationCheckBox.AutoSize = true;
            confirmationCheckBox.ForeColor = Navy;
            confirmationCheckBox.Anchor = AnchorStyles.Left;
            card.SetColumnSpan(confirmationCheckBox, 2);
            card.Controls.Add(confirmationCheckBox, 0, 3);
            root.Controls.Add(card, 0, 1);

            var footer = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 4,
                RowCount = 1,
                Padding = new Padding(0, 10, 0, 0)
            };
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 10));
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
            footer.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
            var cancelButton = new Button
            {
                Text = "Abbrechen",
                DialogResult = DialogResult.Cancel,
                Dock = DockStyle.Fill,
                FlatStyle = FlatStyle.Flat,
                BackColor = Color.White,
                ForeColor = Navy,
                Margin = new Padding(0)
            };
            cancelButton.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230);
            var saveButton = new Button
            {
                Text = "Speichern",
                DialogResult = DialogResult.OK,
                Dock = DockStyle.Fill,
                FlatStyle = FlatStyle.Flat,
                BackColor = Blue,
                ForeColor = Color.White,
                Margin = new Padding(0)
            };
            saveButton.FlatAppearance.BorderSize = 0;
            footer.Controls.Add(cancelButton, 1, 0);
            footer.Controls.Add(saveButton, 3, 0);
            root.Controls.Add(footer, 0, 2);
            AcceptButton = saveButton;
            CancelButton = cancelButton;
        }
    }

    internal sealed class ImportWizardForm : Form
    {
        private readonly string csvPath;
        private readonly Color Navy = Color.FromArgb(24, 38, 58);
        private readonly Color Blue = Color.FromArgb(41, 112, 255);
        private readonly Color Background = Color.FromArgb(244, 247, 251);
        private readonly Color Muted = Color.FromArgb(94, 108, 128);
        private readonly CheckBox headerCheckBox = new CheckBox();
        private readonly Label formatLabel = new Label();
        private readonly Label selectionLabel = new Label();
        private readonly CheckedListBox columnList = new CheckedListBox();
        private readonly DataGridView previewGrid = new DataGridView();
        private readonly Button importButton = new Button();
        private CsvDocument loadedDocument;

        public CsvDocument SelectedDocument { get; private set; }

        public ImportWizardForm(string path)
        {
            csvPath = path;
            Text = "PANDA Import-Assistent";
            StartPosition = FormStartPosition.CenterParent;
            MinimumSize = new Size(900, 600);
            Size = new Size(1040, 680);
            BackColor = Background;
            Font = new Font("Segoe UI", 9F);
            Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application;
            BuildLayout();
            ReloadPreview(true);
        }

        private void BuildLayout()
        {
            var root = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 1,
                RowCount = 4,
                Padding = new Padding(22, 18, 22, 18),
                BackColor = Background
            };
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 72));
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 72));
            root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 58));
            Controls.Add(root);

            var titlePanel = new Panel { Dock = DockStyle.Fill };
            titlePanel.Controls.Add(new Label
            {
                Text = "CSV-Import vorbereiten",
                Font = new Font("Segoe UI Semibold", 18F),
                ForeColor = Navy,
                AutoSize = true,
                Location = new Point(0, 0)
            });
            titlePanel.Controls.Add(new Label
            {
                Text = "Prüfe das Format und wähle die Spalten aus, die PANDA übernehmen soll.",
                ForeColor = Muted,
                AutoSize = true,
                Location = new Point(2, 39)
            });
            root.Controls.Add(titlePanel, 0, 0);

            var filePanel = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 3,
                RowCount = 2,
                BackColor = Color.White,
                Padding = new Padding(14, 8, 14, 8),
                Margin = new Padding(0, 0, 0, 12)
            };
            filePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 55));
            filePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 240));
            filePanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 45));
            filePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
            filePanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 24));
            var stepOne = new Label
            {
                Text = "1   DATEI UND FORMAT",
                Font = new Font("Segoe UI Semibold", 9F),
                ForeColor = Blue,
                AutoSize = true,
                Anchor = AnchorStyles.Left
            };
            filePanel.Controls.Add(stepOne, 0, 0);
            filePanel.SetColumnSpan(stepOne, 3);
            filePanel.Controls.Add(new Label
            {
                Text = Path.GetFileName(csvPath),
                ForeColor = Navy,
                AutoEllipsis = true,
                Dock = DockStyle.Fill,
                TextAlign = ContentAlignment.MiddleLeft
            }, 0, 1);
            headerCheckBox.Text = "Erste Zeile enthält Überschriften";
            headerCheckBox.Checked = true;
            headerCheckBox.AutoSize = true;
            headerCheckBox.ForeColor = Navy;
            headerCheckBox.Anchor = AnchorStyles.Left;
            headerCheckBox.CheckedChanged += delegate { ReloadPreview(false); };
            filePanel.Controls.Add(headerCheckBox, 1, 1);
            formatLabel.ForeColor = Muted;
            formatLabel.AutoEllipsis = true;
            formatLabel.Dock = DockStyle.Fill;
            formatLabel.TextAlign = ContentAlignment.MiddleRight;
            filePanel.Controls.Add(formatLabel, 2, 1);
            root.Controls.Add(filePanel, 0, 1);

            var content = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 2,
                RowCount = 2,
                BackColor = Background,
                Margin = new Padding(0)
            };
            content.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 280));
            content.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
            content.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
            content.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
            content.Controls.Add(CreateSectionHeader("2   SPALTEN AUSWÄHLEN", false), 0, 0);
            content.Controls.Add(CreateSectionHeader("VORSCHAU DER DATEI", true), 1, 0);
            root.Controls.Add(content, 0, 2);

            var selectionPanel = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 2,
                RowCount = 3,
                BackColor = Color.White,
                Padding = new Padding(12, 10, 12, 12),
                Margin = new Padding(0, 0, 7, 0)
            };
            selectionPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
            selectionPanel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
            selectionPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 30));
            selectionPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
            selectionPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 26));
            var allButton = CreateLinkButton("Alle auswählen");
            allButton.Click += delegate { SetAllColumns(true); };
            var noneButton = CreateLinkButton("Auswahl aufheben");
            noneButton.Click += delegate { SetAllColumns(false); };
            selectionPanel.Controls.Add(allButton, 0, 0);
            selectionPanel.Controls.Add(noneButton, 1, 0);
            columnList.Dock = DockStyle.Fill;
            columnList.BorderStyle = BorderStyle.None;
            columnList.CheckOnClick = true;
            columnList.ForeColor = Navy;
            columnList.BackColor = Color.White;
            columnList.HorizontalScrollbar = true;
            columnList.ItemCheck += delegate(object sender, ItemCheckEventArgs args)
            {
                if (args.Index >= 0 && args.Index < previewGrid.Columns.Count)
                    previewGrid.Columns[args.Index].Visible = args.NewValue == CheckState.Checked;
                if (IsHandleCreated)
                    BeginInvoke(new Action(UpdateSelectionStatus));
            };
            selectionPanel.SetColumnSpan(columnList, 2);
            selectionPanel.Controls.Add(columnList, 0, 1);
            selectionLabel.ForeColor = Muted;
            selectionLabel.Dock = DockStyle.Fill;
            selectionLabel.TextAlign = ContentAlignment.MiddleLeft;
            selectionPanel.SetColumnSpan(selectionLabel, 2);
            selectionPanel.Controls.Add(selectionLabel, 0, 2);
            content.Controls.Add(selectionPanel, 0, 1);

            ConfigurePreviewGrid();
            content.Controls.Add(previewGrid, 1, 1);

            var footer = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 4,
                RowCount = 1,
                Padding = new Padding(0, 12, 0, 0)
            };
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 140));
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 10));
            footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 140));
            footer.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
            importButton.Text = "Importieren";
            importButton.Dock = DockStyle.Fill;
            importButton.Margin = new Padding(0);
            importButton.FlatStyle = FlatStyle.Flat;
            importButton.FlatAppearance.BorderSize = 0;
            importButton.BackColor = Blue;
            importButton.ForeColor = Color.White;
            importButton.Cursor = Cursors.Hand;
            importButton.Click += delegate { FinishImport(); };
            var cancelButton = new Button
            {
                Text = "Abbrechen",
                DialogResult = DialogResult.Cancel,
                Dock = DockStyle.Fill,
                FlatStyle = FlatStyle.Flat,
                BackColor = Color.White,
                ForeColor = Navy,
                Margin = new Padding(0)
            };
            cancelButton.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230);
            footer.Controls.Add(cancelButton, 1, 0);
            footer.Controls.Add(importButton, 3, 0);
            root.Controls.Add(footer, 0, 3);
            AcceptButton = importButton;
            CancelButton = cancelButton;
        }

        private Panel CreateSectionHeader(string text, bool preview)
        {
            var panel = new Panel
            {
                Dock = DockStyle.Fill,
                BackColor = preview ? Color.FromArgb(236, 243, 255) : Color.White,
                Margin = preview ? new Padding(7, 0, 0, 0) : new Padding(0, 0, 7, 0)
            };
            panel.Controls.Add(new Label
            {
                Text = text,
                Font = new Font("Segoe UI Semibold", 9F),
                ForeColor = preview ? Blue : Navy,
                AutoSize = true,
                Location = new Point(12, 12)
            });
            return panel;
        }

        private Button CreateLinkButton(string text)
        {
            var button = new Button
            {
                Text = text,
                Dock = DockStyle.Fill,
                FlatStyle = FlatStyle.Flat,
                BackColor = Color.White,
                ForeColor = Blue,
                Cursor = Cursors.Hand,
                Margin = new Padding(0, 0, 4, 4)
            };
            button.FlatAppearance.BorderSize = 0;
            return button;
        }

        private void ConfigurePreviewGrid()
        {
            previewGrid.Dock = DockStyle.Fill;
            previewGrid.Margin = new Padding(7, 0, 0, 0);
            previewGrid.BackgroundColor = Color.White;
            previewGrid.BorderStyle = BorderStyle.None;
            previewGrid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
            previewGrid.GridColor = Color.FromArgb(228, 234, 242);
            previewGrid.RowHeadersVisible = false;
            previewGrid.ColumnHeadersHeight = 36;
            previewGrid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 253);
            previewGrid.ColumnHeadersDefaultCellStyle.ForeColor = Navy;
            previewGrid.ColumnHeadersDefaultCellStyle.Font = new Font("Segoe UI Semibold", 9F);
            previewGrid.EnableHeadersVisualStyles = false;
            previewGrid.DefaultCellStyle.BackColor = Color.White;
            previewGrid.DefaultCellStyle.ForeColor = Navy;
            previewGrid.DefaultCellStyle.SelectionBackColor = Color.White;
            previewGrid.DefaultCellStyle.SelectionForeColor = Navy;
            previewGrid.RowTemplate.Height = 28;
            previewGrid.AllowUserToAddRows = false;
            previewGrid.AllowUserToDeleteRows = false;
            previewGrid.AllowUserToOrderColumns = false;
            previewGrid.ReadOnly = true;
            previewGrid.MultiSelect = false;
            previewGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
        }

        private void ReloadPreview(bool firstLoad)
        {
            try
            {
                loadedDocument = CsvCodec.Load(csvPath, headerCheckBox.Checked);
                if (loadedDocument.Headers.Count == 0)
                    throw new InvalidDataException("Die Datei enthält keine auswertbaren CSV-Daten.");
                formatLabel.Text = loadedDocument.Rows.Count + " Zeilen  •  " + loadedDocument.Headers.Count + " Spalten  •  " + DelimiterName(loadedDocument.Delimiter);
                columnList.Items.Clear();
                foreach (string header in loadedDocument.Headers)
                    columnList.Items.Add(header, true);
                PopulatePreview();
                UpdateSelectionStatus();
            }
            catch (Exception exception)
            {
                if (!firstLoad)
                    MessageBox.Show(this, "Die Vorschau konnte nicht aktualisiert werden.\r\n\r\n" + exception.Message, "Import-Assistent", MessageBoxButtons.OK, MessageBoxIcon.Error);
                importButton.Enabled = false;
            }
        }

        private void PopulatePreview()
        {
            previewGrid.SuspendLayout();
            previewGrid.Columns.Clear();
            previewGrid.Rows.Clear();
            for (int column = 0; column < loadedDocument.Headers.Count; column++)
            {
                previewGrid.Columns.Add("Preview" + column, loadedDocument.Headers[column]);
                previewGrid.Columns[column].SortMode = DataGridViewColumnSortMode.NotSortable;
            }
            foreach (var row in loadedDocument.Rows.Take(50))
                previewGrid.Rows.Add(row.Cast<object>().ToArray());
            previewGrid.ClearSelection();
            previewGrid.ResumeLayout();
        }

        private void SetAllColumns(bool selected)
        {
            for (int index = 0; index < columnList.Items.Count; index++)
                columnList.SetItemChecked(index, selected);
            BeginInvoke(new Action(UpdateSelectionStatus));
        }

        private void UpdateSelectionStatus()
        {
            int count = columnList.CheckedIndices.Count;
            selectionLabel.Text = count + " von " + columnList.Items.Count + " Spalten ausgewählt";
            importButton.Enabled = count > 0;
        }

        private void FinishImport()
        {
            var selected = columnList.CheckedIndices.Cast<int>().ToList();
            if (selected.Count == 0)
            {
                MessageBox.Show(this, "Bitte wähle mindestens eine Spalte aus.", "Keine Spalte ausgewählt", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }
            SelectedDocument = CsvCodec.SelectColumns(loadedDocument, selected);
            DialogResult = DialogResult.OK;
            Close();
        }

        private static string DelimiterName(char delimiter)
        {
            if (delimiter == ';') return "Semikolon";
            if (delimiter == ',') return "Komma";
            if (delimiter == '\t') return "Tabulator";
            return delimiter.ToString();
        }

        internal void UncheckLastColumnForPreview()
        {
            if (columnList.Items.Count > 1)
                columnList.SetItemChecked(columnList.Items.Count - 1, false);
        }
    }

    internal sealed class MainForm : Form
    {
        private readonly Color Navy = Color.FromArgb(24, 38, 58);
        private readonly Color Blue = Color.FromArgb(41, 112, 255);
        private readonly Color PaleBlue = Color.FromArgb(236, 243, 255);
        private readonly Color Background = Color.FromArgb(244, 247, 251);
        private readonly Color Muted = Color.FromArgb(94, 108, 128);

        private readonly DataGridView originalGrid = new DataGridView();
        private readonly DataGridView resultGrid = new DataGridView();
        private readonly ComboBox scopeComboBox = new ComboBox();
        private readonly NumericUpDown stepNumeric = new NumericUpDown();
        private readonly Label statusLabel = new Label();
        private readonly Label fileLabel = new Label();
        private readonly Button exportButton = new Button();
        private readonly Button resetButton = new Button();
        private readonly Button settingsButton = new Button();

        private CsvDocument document;
        private string importedPath;
        private AppSettings appSettings = AppSettings.Load();

        public MainForm()
        {
            Text = "PANDA – Pseudonymisierung alphanumerischer Nutzdaten";
            StartPosition = FormStartPosition.CenterScreen;
            MinimumSize = new Size(1050, 680);
            Size = new Size(1320, 820);
            BackColor = Background;
            Font = new Font("Segoe UI", 9F);
            Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application;

            BuildLayout();
            ConfigureGrid(originalGrid, true);
            ConfigureGrid(resultGrid, false);
            SetDocumentControlsEnabled(false);
        }

        private void BuildLayout()
        {
            var root = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 1,
                RowCount = 4,
                Padding = new Padding(20, 18, 20, 16),
                BackColor = Background
            };
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 68));
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 92));
            root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
            root.RowStyles.Add(new RowStyle(SizeType.Absolute, 40));
            Controls.Add(root);

            var header = new Panel { Dock = DockStyle.Fill, BackColor = Background };
            var title = new Label
            {
                Text = "PANDA",
                ForeColor = Navy,
                Font = new Font("Segoe UI Semibold", 19F),
                AutoSize = true,
                Location = new Point(0, 0)
            };
            var subtitle = new Label
            {
                Text = "Pseudonymisierung alphanumerischer Nutzdaten durch Alphabetverschiebung",
                ForeColor = Muted,
                AutoSize = true,
                Location = new Point(2, 39)
            };
            header.Controls.Add(title);
            header.Controls.Add(subtitle);
            root.Controls.Add(header, 0, 0);

            var toolbar = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 9,
                RowCount = 2,
                Padding = new Padding(12, 10, 12, 8),
                BackColor = Color.White,
                Margin = new Padding(0, 0, 0, 12)
            };
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 148));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 160));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 76));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 135));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 135));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 108));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 122));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 116));
            toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
            toolbar.RowStyles.Add(new RowStyle(SizeType.Absolute, 34));
            toolbar.RowStyles.Add(new RowStyle(SizeType.Absolute, 28));
            root.Controls.Add(toolbar, 0, 1);

            var importButton = CreateButton("Importieren", Blue, Color.White);
            importButton.Click += delegate { ImportCsv(); };
            toolbar.Controls.Add(importButton, 0, 0);

            scopeComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
            scopeComboBox.Items.AddRange(new object[] { "Markierte Zellen", "Aktuelle Zelle", "Alle Einträge" });
            scopeComboBox.SelectedIndex = 0;
            scopeComboBox.Dock = DockStyle.Fill;
            scopeComboBox.Margin = new Padding(7, 3, 7, 3);
            toolbar.Controls.Add(scopeComboBox, 1, 0);

            stepNumeric.Minimum = 1;
            stepNumeric.Maximum = 25;
            stepNumeric.Value = Math.Max(1, Math.Min(25, appSettings.DefaultShift));
            stepNumeric.Dock = DockStyle.Fill;
            stepNumeric.TextAlign = HorizontalAlignment.Center;
            stepNumeric.Margin = new Padding(7, 3, 7, 3);
            toolbar.Controls.Add(stepNumeric, 2, 0);

            var upButton = CreateButton("Hochzählen  +", Color.FromArgb(29, 157, 105), Color.White);
            upButton.Click += delegate { ApplyShift((int)stepNumeric.Value); };
            toolbar.Controls.Add(upButton, 3, 0);

            var downButton = CreateButton("Runterzählen  −", Color.FromArgb(230, 91, 84), Color.White);
            downButton.Click += delegate { ApplyShift(-(int)stepNumeric.Value); };
            toolbar.Controls.Add(downButton, 4, 0);

            resetButton.Text = "Zurücksetzen";
            StyleSecondaryButton(resetButton);
            resetButton.Click += delegate { ResetResults(); };
            toolbar.Controls.Add(resetButton, 5, 0);

            exportButton.Text = "CSV exportieren";
            StyleSecondaryButton(exportButton);
            exportButton.Click += delegate { ExportCsv(); };
            toolbar.Controls.Add(exportButton, 6, 0);

            settingsButton.Text = "Einstellungen";
            StyleSecondaryButton(settingsButton);
            settingsButton.Click += delegate { OpenSettings(); };
            toolbar.Controls.Add(settingsButton, 7, 0);

            fileLabel.Text = "Noch keine CSV geladen";
            fileLabel.ForeColor = Muted;
            fileLabel.AutoEllipsis = true;
            fileLabel.Dock = DockStyle.Fill;
            fileLabel.TextAlign = ContentAlignment.MiddleLeft;
            toolbar.SetColumnSpan(fileLabel, 4);
            toolbar.Controls.Add(fileLabel, 0, 1);

            var hint = new Label
            {
                Text = "Tipp: Strg oder Umschalt gedrückt halten, um mehrere Zellen zu markieren.",
                ForeColor = Muted,
                AutoEllipsis = true,
                Dock = DockStyle.Fill,
                TextAlign = ContentAlignment.MiddleRight
            };
            toolbar.SetColumnSpan(hint, 5);
            toolbar.Controls.Add(hint, 4, 1);

            var grids = new TableLayoutPanel
            {
                Dock = DockStyle.Fill,
                ColumnCount = 2,
                RowCount = 2,
                BackColor = Background,
                Margin = new Padding(0)
            };
            grids.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
            grids.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50));
            grids.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
            grids.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
            root.Controls.Add(grids, 0, 2);

            grids.Controls.Add(CreateGridHeader("ORIGINAL", "Importierte CSV-Werte", false), 0, 0);
            grids.Controls.Add(CreateGridHeader("ERGEBNIS", "Veränderte Werte", true), 1, 0);

            originalGrid.Margin = new Padding(0, 0, 7, 0);
            resultGrid.Margin = new Padding(7, 0, 0, 0);
            grids.Controls.Add(originalGrid, 0, 1);
            grids.Controls.Add(resultGrid, 1, 1);

            var statusPanel = new Panel { Dock = DockStyle.Fill, BackColor = Background };
            statusLabel.Text = "Bereit – bitte eine CSV-Datei importieren.";
            statusLabel.ForeColor = Muted;
            statusLabel.AutoSize = true;
            statusLabel.Location = new Point(2, 12);
            statusPanel.Controls.Add(statusLabel);
            root.Controls.Add(statusPanel, 0, 3);
        }

        private Panel CreateGridHeader(string eyebrow, string caption, bool result)
        {
            var panel = new Panel
            {
                Dock = DockStyle.Fill,
                BackColor = result ? PaleBlue : Color.White,
                Margin = result ? new Padding(7, 0, 0, 0) : new Padding(0, 0, 7, 0)
            };
            var title = new Label
            {
                Text = eyebrow + "   " + caption,
                Font = new Font("Segoe UI Semibold", 10F),
                ForeColor = result ? Blue : Navy,
                AutoSize = true,
                Location = new Point(12, 11)
            };
            panel.Controls.Add(title);
            return panel;
        }

        private Button CreateButton(string text, Color background, Color foreground)
        {
            var button = new Button
            {
                Text = text,
                Dock = DockStyle.Fill,
                FlatStyle = FlatStyle.Flat,
                BackColor = background,
                ForeColor = foreground,
                Cursor = Cursors.Hand,
                Margin = new Padding(4, 2, 4, 2)
            };
            button.FlatAppearance.BorderSize = 0;
            return button;
        }

        private void StyleSecondaryButton(Button button)
        {
            button.Dock = DockStyle.Fill;
            button.FlatStyle = FlatStyle.Flat;
            button.BackColor = Color.White;
            button.ForeColor = Navy;
            button.Cursor = Cursors.Hand;
            button.Margin = new Padding(4, 2, 4, 2);
            button.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230);
        }

        private void OpenSettings()
        {
            using (var dialog = new SettingsForm(appSettings))
            {
                if (dialog.ShowDialog(this) != DialogResult.OK)
                    return;

                appSettings.DefaultShift = dialog.DefaultShift;
                appSettings.ConfirmBeforeShift = dialog.ConfirmBeforeShift;
                try
                {
                    appSettings.Save();
                    stepNumeric.Value = appSettings.DefaultShift;
                    statusLabel.Text = "Einstellungen gespeichert. Standard-Zählwert: " + appSettings.DefaultShift + ".";
                    statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
                }
                catch (Exception exception)
                {
                    MessageBox.Show(this, "Die Einstellungen konnten nicht gespeichert werden.\r\n\r\n" + exception.Message, "Speichern fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void ConfigureGrid(DataGridView grid, bool selectable)
        {
            grid.Dock = DockStyle.Fill;
            grid.BackgroundColor = Color.White;
            grid.BorderStyle = BorderStyle.None;
            grid.CellBorderStyle = DataGridViewCellBorderStyle.SingleHorizontal;
            grid.GridColor = Color.FromArgb(228, 234, 242);
            grid.RowHeadersVisible = true;
            grid.RowHeadersWidth = selectable ? 74 : 50;
            grid.RowHeadersDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 253);
            grid.RowHeadersDefaultCellStyle.ForeColor = Muted;
            grid.RowHeadersDefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleCenter;
            grid.ColumnHeadersHeight = 36;
            grid.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.DisableResizing;
            grid.ColumnHeadersDefaultCellStyle.BackColor = Color.FromArgb(248, 250, 253);
            grid.ColumnHeadersDefaultCellStyle.ForeColor = Navy;
            grid.ColumnHeadersDefaultCellStyle.Font = new Font("Segoe UI Semibold", 9F);
            grid.ColumnHeadersDefaultCellStyle.SelectionBackColor = Color.FromArgb(248, 250, 253);
            grid.EnableHeadersVisualStyles = false;
            grid.DefaultCellStyle.BackColor = Color.White;
            grid.DefaultCellStyle.ForeColor = Navy;
            grid.DefaultCellStyle.SelectionBackColor = selectable ? Color.FromArgb(214, 227, 255) : Color.White;
            grid.DefaultCellStyle.SelectionForeColor = Navy;
            grid.DefaultCellStyle.Padding = new Padding(4, 2, 4, 2);
            grid.RowTemplate.Height = 30;
            grid.AllowUserToAddRows = false;
            grid.AllowUserToDeleteRows = false;
            grid.AllowUserToOrderColumns = false;
            grid.AllowUserToResizeRows = false;
            grid.MultiSelect = true;
            grid.SelectionMode = DataGridViewSelectionMode.CellSelect;
            grid.ReadOnly = true;
            grid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
            grid.ClipboardCopyMode = DataGridViewClipboardCopyMode.EnableWithoutHeaderText;
            grid.DataError += delegate { };

            if (selectable)
            {
                grid.RowPostPaint += DrawOriginalRowHeader;
                grid.RowHeaderMouseClick += ToggleOriginalRow;
                grid.Scroll += delegate { SyncScroll(originalGrid, resultGrid); };
            }
            else
            {
                grid.MultiSelect = false;
                grid.TabStop = false;
                grid.SelectionChanged += delegate
                {
                    if (grid.SelectedCells.Count > 0)
                        grid.ClearSelection();
                };
                grid.CellMouseDown += delegate(object sender, DataGridViewCellMouseEventArgs args)
                {
                    if (args.RowIndex < 0 || args.ColumnIndex < 0)
                        return;
                    originalGrid.ClearSelection();
                    originalGrid.CurrentCell = originalGrid.Rows[args.RowIndex].Cells[args.ColumnIndex];
                    originalGrid.CurrentCell.Selected = true;
                    originalGrid.Focus();
                    BeginInvoke(new Action(delegate
                    {
                        grid.ClearSelection();
                        grid.CurrentCell = null;
                    }));
                };
                grid.Scroll += delegate { SyncScroll(resultGrid, originalGrid); };
            }
        }

        private void DrawOriginalRowHeader(object sender, DataGridViewRowPostPaintEventArgs args)
        {
            var grid = (DataGridView)sender;
            var bounds = new Rectangle(0, args.RowBounds.Top, grid.RowHeadersWidth, args.RowBounds.Height);
            using (var backgroundBrush = new SolidBrush(Color.FromArgb(248, 250, 253)))
                args.Graphics.FillRectangle(backgroundBrush, bounds);
            using (var linePen = new Pen(Color.FromArgb(228, 234, 242)))
                args.Graphics.DrawLine(linePen, bounds.Left, bounds.Bottom - 1, bounds.Right, bounds.Bottom - 1);

            bool selected = grid.Rows[args.RowIndex].Tag is bool && (bool)grid.Rows[args.RowIndex].Tag;
            int checkSize = 14;
            var checkPoint = new Point(8, bounds.Top + (bounds.Height - checkSize) / 2);
            CheckBoxRenderer.DrawCheckBox(args.Graphics, checkPoint, selected ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
            var numberBounds = new Rectangle(29, bounds.Top, bounds.Width - 32, bounds.Height);
            TextRenderer.DrawText(args.Graphics, (args.RowIndex + 1).ToString(), grid.Font, numberBounds, Muted, TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.NoPadding);
        }

        private void ToggleOriginalRow(object sender, DataGridViewCellMouseEventArgs args)
        {
            if (args.RowIndex < 0 || args.RowIndex >= originalGrid.Rows.Count)
                return;
            var row = originalGrid.Rows[args.RowIndex];
            bool wasSelected = row.Tag is bool && (bool)row.Tag;
            bool isSelected = !wasSelected;
            row.Tag = isSelected;
            foreach (DataGridViewCell cell in row.Cells)
                cell.Selected = isSelected;
            originalGrid.InvalidateRow(args.RowIndex);
            BeginInvoke(new Action(RefreshCheckedRowSelections));
            statusLabel.Text = isSelected
                ? "Zeile " + (args.RowIndex + 1) + " vollständig ausgewählt."
                : "Zeile " + (args.RowIndex + 1) + " aus der vollständigen Auswahl entfernt.";
            statusLabel.ForeColor = Muted;
        }

        private void RefreshCheckedRowSelections()
        {
            foreach (DataGridViewRow row in originalGrid.Rows)
            {
                bool isSelected = row.Tag is bool && (bool)row.Tag;
                if (!isSelected)
                    continue;
                foreach (DataGridViewCell cell in row.Cells)
                    cell.Selected = true;
            }
            originalGrid.Invalidate();
        }

        private void ImportCsv()
        {
            using (var dialog = new OpenFileDialog())
            {
                dialog.Title = "CSV-Datei importieren";
                dialog.Filter = "CSV-Dateien (*.csv)|*.csv|Textdateien (*.txt)|*.txt|Alle Dateien (*.*)|*.*";
                dialog.CheckFileExists = true;
                dialog.Multiselect = false;
                if (dialog.ShowDialog(this) != DialogResult.OK)
                    return;

                try
                {
                    using (var wizard = new ImportWizardForm(dialog.FileName))
                    {
                        if (wizard.ShowDialog(this) != DialogResult.OK)
                            return;
                        document = wizard.SelectedDocument;
                    }
                    importedPath = dialog.FileName;
                    PopulateGrids();
                    fileLabel.Text = Path.GetFileName(dialog.FileName) + "  •  " + document.Rows.Count + " Zeilen  •  " + document.Headers.Count + " importierte Spalten  •  " + DelimiterName(document.Delimiter);
                    statusLabel.Text = "Import erfolgreich. Wähle links Zellen aus oder nutze ‚Alle Einträge‘.";
                    statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
                    SetDocumentControlsEnabled(true);
                }
                catch (Exception exception)
                {
                    MessageBox.Show(this, "Die CSV-Datei konnte nicht importiert werden.\r\n\r\n" + exception.Message, "Import fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    statusLabel.Text = "Import fehlgeschlagen.";
                    statusLabel.ForeColor = Color.FromArgb(190, 60, 55);
                }
            }
        }

        private string DelimiterName(char delimiter)
        {
            if (delimiter == ';') return "Semikolon";
            if (delimiter == ',') return "Komma";
            if (delimiter == '\t') return "Tabulator";
            return delimiter.ToString();
        }

        private void PopulateGrids()
        {
            originalGrid.SuspendLayout();
            resultGrid.SuspendLayout();
            originalGrid.Columns.Clear();
            resultGrid.Columns.Clear();
            originalGrid.Rows.Clear();
            resultGrid.Rows.Clear();

            for (int column = 0; column < document.Headers.Count; column++)
            {
                string key = "Column" + column;
                originalGrid.Columns.Add(key, document.Headers[column]);
                resultGrid.Columns.Add(key, document.Headers[column]);
                originalGrid.Columns[column].SortMode = DataGridViewColumnSortMode.NotSortable;
                resultGrid.Columns[column].SortMode = DataGridViewColumnSortMode.NotSortable;
            }

            foreach (var row in document.Rows)
            {
                originalGrid.Rows.Add(row.Cast<object>().ToArray());
                resultGrid.Rows.Add(row.Cast<object>().ToArray());
            }

            for (int row = 0; row < document.Rows.Count; row++)
            {
                originalGrid.Rows[row].HeaderCell.Value = (row + 1).ToString();
                originalGrid.Rows[row].Tag = false;
                resultGrid.Rows[row].HeaderCell.Value = (row + 1).ToString();
            }

            originalGrid.ClearSelection();
            resultGrid.ClearSelection();
            resultGrid.CurrentCell = null;
            originalGrid.ResumeLayout();
            resultGrid.ResumeLayout();
        }

        private void ApplyShift(int amount)
        {
            if (document == null)
                return;

            var cells = GetTargetCells();
            if (cells.Count == 0)
            {
                MessageBox.Show(this, "Bitte markiere mindestens eine Zelle in der linken Tabelle oder wähle als Bereich ‚Alle Einträge‘.", "Keine Auswahl", MessageBoxButtons.OK, MessageBoxIcon.Information);
                return;
            }

            if (appSettings.ConfirmBeforeShift)
            {
                string message = BuildConfirmationMessage(amount, cells.Count, scopeComboBox.SelectedIndex == 2);
                DialogResult confirmation = MessageBox.Show(this, message, "Umwandlung bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
                if (confirmation != DialogResult.Yes)
                {
                    statusLabel.Text = "Umwandlung abgebrochen.";
                    statusLabel.ForeColor = Muted;
                    return;
                }
            }

            int changed = 0;
            foreach (var coordinate in cells)
            {
                var cell = resultGrid.Rows[coordinate.Item1].Cells[coordinate.Item2];
                string before = Convert.ToString(cell.Value) ?? string.Empty;
                string after = LetterShifter.Shift(before, amount);
                cell.Value = after;
                if (!string.Equals(before, after, StringComparison.Ordinal))
                    changed++;
            }

            statusLabel.Text = changed + " von " + cells.Count + " Zellen verändert (" + (amount > 0 ? "+" : string.Empty) + amount + ").";
            statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
        }

        internal static string BuildConfirmationMessage(int amount, int cellCount, bool allValues)
        {
            string subject = allValues ? "Alle Werte" : "Die ausgewählten Werte";
            string direction = amount > 0 ? "hochgezählt" : "runtergezählt";
            return subject + " werden um " + Math.Abs(amount) + " " + direction + ".\r\n\r\n"
                + "Gewählter Zählwert: " + Math.Abs(amount) + "\r\n"
                + "Betroffene Zellen: " + cellCount + "\r\n\r\n"
                + "Möchtest du die Umwandlung durchführen?";
        }

        private List<Tuple<int, int>> GetTargetCells()
        {
            var result = new List<Tuple<int, int>>();
            if (scopeComboBox.SelectedIndex == 2)
            {
                for (int row = 0; row < resultGrid.Rows.Count; row++)
                    for (int column = 0; column < resultGrid.Columns.Count; column++)
                        result.Add(Tuple.Create(row, column));
                return result;
            }

            if (scopeComboBox.SelectedIndex == 1)
            {
                var current = originalGrid.CurrentCell;
                if (current != null)
                    result.Add(Tuple.Create(current.RowIndex, current.ColumnIndex));
                return result;
            }

            foreach (DataGridViewCell cell in originalGrid.SelectedCells)
                result.Add(Tuple.Create(cell.RowIndex, cell.ColumnIndex));
            foreach (DataGridViewRow row in originalGrid.Rows)
            {
                bool wholeRow = row.Tag is bool && (bool)row.Tag;
                if (!wholeRow)
                    continue;
                for (int column = 0; column < resultGrid.Columns.Count; column++)
                    result.Add(Tuple.Create(row.Index, column));
            }
            return result.Distinct().ToList();
        }

        private void ResetResults()
        {
            if (document == null)
                return;
            for (int row = 0; row < document.Rows.Count; row++)
                for (int column = 0; column < document.Headers.Count; column++)
                    resultGrid.Rows[row].Cells[column].Value = document.Rows[row][column];
            statusLabel.Text = "Alle Ergebnisse wurden auf die importierten Werte zurückgesetzt.";
            statusLabel.ForeColor = Muted;
        }

        private void ExportCsv()
        {
            if (document == null)
                return;

            using (var dialog = new SaveFileDialog())
            {
                dialog.Title = "Veränderte CSV exportieren";
                dialog.Filter = "CSV-Dateien (*.csv)|*.csv|Alle Dateien (*.*)|*.*";
                dialog.DefaultExt = "csv";
                dialog.AddExtension = true;
                string sourceName = string.IsNullOrEmpty(importedPath) ? "ergebnis" : Path.GetFileNameWithoutExtension(importedPath) + "_veraendert";
                dialog.FileName = sourceName + ".csv";
                dialog.InitialDirectory = string.IsNullOrEmpty(importedPath) ? Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) : Path.GetDirectoryName(importedPath);
                if (dialog.ShowDialog(this) != DialogResult.OK)
                    return;

                try
                {
                    var rows = new List<IList<string>>();
                    foreach (DataGridViewRow gridRow in resultGrid.Rows)
                    {
                        var values = new List<string>();
                        foreach (DataGridViewCell cell in gridRow.Cells)
                            values.Add(Convert.ToString(cell.Value) ?? string.Empty);
                        rows.Add(values);
                    }
                    CsvCodec.Save(dialog.FileName, document, rows);
                    statusLabel.Text = "Export erfolgreich: " + dialog.FileName;
                    statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
                    MessageBox.Show(this, "Die veränderte CSV wurde erfolgreich gespeichert.", "Export abgeschlossen", MessageBoxButtons.OK, MessageBoxIcon.Information);
                }
                catch (Exception exception)
                {
                    MessageBox.Show(this, "Die CSV-Datei konnte nicht gespeichert werden.\r\n\r\n" + exception.Message, "Export fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void SyncScroll(DataGridView source, DataGridView target)
        {
            if (source.Rows.Count == 0 || target.Rows.Count == 0)
                return;
            try
            {
                target.FirstDisplayedScrollingRowIndex = source.FirstDisplayedScrollingRowIndex;
                target.HorizontalScrollingOffset = source.HorizontalScrollingOffset;
            }
            catch (ArgumentOutOfRangeException)
            {
            }
        }

        private void SetDocumentControlsEnabled(bool enabled)
        {
            scopeComboBox.Enabled = enabled;
            if (enabled && scopeComboBox.SelectedIndex < 0 && scopeComboBox.Items.Count > 0)
                scopeComboBox.SelectedIndex = 0;
            stepNumeric.Enabled = enabled;
            exportButton.Enabled = enabled;
            resetButton.Enabled = enabled;
            scopeComboBox.Refresh();
        }

        internal void LoadPreviewData()
        {
            document = new CsvDocument { Delimiter = ';', FirstRowIsHeader = true };
            document.Headers.AddRange(new[] { "Kundennummer", "Vorname", "Nachname", "Ort" });
            document.Rows.Add(new List<string> { "1001", "Anna", "Meyer", "Berlin" });
            document.Rows.Add(new List<string> { "1002", "Jonas", "Schmidt", "Hamburg" });
            document.Rows.Add(new List<string> { "1003", "Zoe", "Fischer", "München" });
            document.Rows.Add(new List<string> { "1004", "Lena", "Wagner", "Köln" });
            PopulateGrids();
            for (int column = 0; column < document.Headers.Count; column++)
                resultGrid.Rows[0].Cells[column].Value = LetterShifter.Shift(document.Rows[0][column], 1);
            originalGrid.Rows[0].Tag = true;
            RefreshCheckedRowSelections();
            fileLabel.Text = "beispiel.csv  •  4 Zeilen  •  4 Spalten  •  Trennzeichen: Semikolon";
            statusLabel.Text = "3 von 4 Zellen verändert (+1).";
            statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
            SetDocumentControlsEnabled(true);
            scopeComboBox.SelectedIndex = 0;
            scopeComboBox.Refresh();
        }
    }
}
