BenriTool

<Window x:Class="BenriTool.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="BenriTool" Height="720" Width="880"
        MinHeight="640" MinWidth="760"
        Background="#F0F2F5"
        FontFamily="Segoe UI" FontSize="13">
    <Window.Resources>
        <!-- Brushes -->
        <SolidColorBrush x:Key="PrimaryBrush"  Color="#1976D2"/>
        <SolidColorBrush x:Key="SuccessBrush"  Color="#388E3C"/>
        <SolidColorBrush x:Key="FieldBorderBrush" Color="#DADCE0"/>

        <!-- Card -->
        <Style x:Key="CardStyle" TargetType="Border">
            <Setter Property="Background"    Value="White"/>
            <Setter Property="CornerRadius"  Value="8"/>
            <Setter Property="Padding"       Value="18,14"/>
            <Setter Property="Margin"        Value="2,2,2,12"/>
            <Setter Property="Effect">
                <Setter.Value>
                    <DropShadowEffect Color="#000000" Opacity="0.07"
                                      BlurRadius="10" ShadowDepth="2" Direction="270"/>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- TextBox (input) -->
        <Style TargetType="TextBox">
            <Setter Property="Height"                  Value="32"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Padding"                 Value="8,0"/>
            <Setter Property="Background"              Value="White"/>
            <Setter Property="Foreground"              Value="#212121"/>
            <Setter Property="BorderBrush"             Value="#B0B8C4"/>
            <Setter Property="BorderThickness"         Value="1"/>
            <Setter Property="Cursor"                  Value="IBeam"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Border x:Name="Bd"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="4" SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost"
                                          Margin="{TemplateBinding Padding}"
                                          VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsEnabled" Value="False">
                                <Setter TargetName="Bd" Property="Background"   Value="#EFEFEF"/>
                                <Setter TargetName="Bd" Property="BorderBrush"  Value="#D4D4D4"/>
                                <Setter Property="Foreground"                   Value="#AAAAAA"/>
                                <Setter Property="Cursor"                       Value="Arrow"/>
                            </Trigger>
                            <Trigger Property="IsFocused" Value="True">
                                <Setter TargetName="Bd" Property="BorderBrush"     Value="#1976D2"/>
                                <Setter TargetName="Bd" Property="BorderThickness" Value="1.5"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- Log TextBox (dark terminal) -->
        <Style x:Key="LogStyle" TargetType="TextBox">
            <Setter Property="Background"              Value="#1E1E1E"/>
            <Setter Property="Foreground"              Value="#D4D4D4"/>
            <Setter Property="FontFamily"              Value="Consolas"/>
            <Setter Property="FontSize"                Value="11"/>
            <Setter Property="IsReadOnly"              Value="True"/>
            <Setter Property="TextWrapping"            Value="Wrap"/>
            <Setter Property="BorderThickness"         Value="0"/>
            <Setter Property="Padding"                 Value="10"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Border Background="{TemplateBinding Background}" CornerRadius="6">
                            <ScrollViewer x:Name="PART_ContentHost"
                                          Margin="{TemplateBinding Padding}"
                                          VerticalScrollBarVisibility="Auto"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- ComboBox (basic modern) -->
        <Style TargetType="ComboBox">
            <Setter Property="Height"                  Value="32"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
            <Setter Property="Padding"                 Value="8,0"/>
            <Setter Property="Background"              Value="#FAFAFA"/>
            <Setter Property="BorderBrush"             Value="{StaticResource FieldBorderBrush}"/>
        </Style>

        <!-- Browse button (outline) -->
        <Style x:Key="BrowseBtn" TargetType="Button">
            <Setter Property="Height"   Value="32"/>
            <Setter Property="Padding"  Value="12,0"/>
            <Setter Property="MinWidth" Value="62"/>
            <Setter Property="Foreground" Value="#1976D2"/>
            <Setter Property="FontSize" Value="12"/>
            <Setter Property="Cursor"   Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border x:Name="Bd" Background="White" CornerRadius="4"
                                BorderBrush="#1976D2" BorderThickness="1">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#E3F2FD"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#BBDEFB"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- Execute button (green solid) -->
        <Style x:Key="ExecBtn" TargetType="Button">
            <Setter Property="Height"       Value="36"/>
            <Setter Property="MinWidth"     Value="130"/>
            <Setter Property="Padding"      Value="22,0"/>
            <Setter Property="Background"   Value="#388E3C"/>
            <Setter Property="Foreground"   Value="White"/>
            <Setter Property="FontWeight"   Value="SemiBold"/>
            <Setter Property="FontSize"     Value="13"/>
            <Setter Property="Cursor"       Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="6">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#2E7D32"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#1B5E20"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- DryRun button (blue solid) -->
        <Style x:Key="DryRunBtn" TargetType="Button">
            <Setter Property="Height"       Value="36"/>
            <Setter Property="Padding"      Value="18,0"/>
            <Setter Property="Background"   Value="#1976D2"/>
            <Setter Property="Foreground"   Value="White"/>
            <Setter Property="FontWeight"   Value="SemiBold"/>
            <Setter Property="FontSize"     Value="13"/>
            <Setter Property="Cursor"       Value="Hand"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="Button">
                        <Border x:Name="Bd" Background="{TemplateBinding Background}" CornerRadius="6">
                            <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#1565C0"/>
                            </Trigger>
                            <Trigger Property="IsPressed" Value="True">
                                <Setter TargetName="Bd" Property="Background" Value="#0D47A1"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- CheckBox (modern) -->
        <Style TargetType="CheckBox">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Cursor"   Value="Hand"/>
            <Setter Property="Foreground" Value="#212121"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="CheckBox">
                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                            <Border x:Name="Box" Width="16" Height="16" CornerRadius="3"
                                    BorderBrush="{StaticResource FieldBorderBrush}"
                                    BorderThickness="2" Background="White">
                                <Path x:Name="Check" Data="M 2,7 L 6,11 L 13,3"
                                      Stroke="White" StrokeThickness="2"
                                      StrokeStartLineCap="Round" StrokeEndLineCap="Round"
                                      Visibility="Collapsed"/>
                            </Border>
                            <ContentPresenter Margin="7,0,0,0" VerticalAlignment="Center"/>
                        </StackPanel>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="Box"   Property="Background"   Value="#1976D2"/>
                                <Setter TargetName="Box"   Property="BorderBrush"  Value="#1976D2"/>
                                <Setter TargetName="Check" Property="Visibility"   Value="Visible"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Box" Property="BorderBrush" Value="#1976D2"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- RadioButton (modern) -->
        <Style TargetType="RadioButton">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Cursor"   Value="Hand"/>
            <Setter Property="Foreground" Value="#212121"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="RadioButton">
                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center">
                            <Grid Width="16" Height="16">
                                <Ellipse x:Name="Ring" Stroke="{StaticResource FieldBorderBrush}"
                                         StrokeThickness="2" Fill="White"/>
                                <Ellipse x:Name="Dot" Width="8" Height="8"
                                         Fill="#1976D2" Visibility="Collapsed"/>
                            </Grid>
                            <ContentPresenter Margin="7,0,0,0" VerticalAlignment="Center"/>
                        </StackPanel>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsChecked" Value="True">
                                <Setter TargetName="Ring" Property="Stroke"      Value="#1976D2"/>
                                <Setter TargetName="Dot"  Property="Visibility"  Value="Visible"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter TargetName="Ring" Property="Stroke" Value="#1976D2"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- Code editor TextBox (multiline, editable) -->
        <Style x:Key="CodeInputStyle" TargetType="TextBox">
            <Setter Property="AcceptsReturn"                Value="True"/>
            <Setter Property="AcceptsTab"                   Value="True"/>
            <Setter Property="FontFamily"                   Value="Consolas"/>
            <Setter Property="FontSize"                     Value="12"/>
            <Setter Property="TextWrapping"                 Value="NoWrap"/>
            <Setter Property="VerticalScrollBarVisibility"  Value="Auto"/>
            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
            <Setter Property="VerticalContentAlignment"     Value="Top"/>
            <Setter Property="Background"                   Value="White"/>
            <Setter Property="Foreground"                   Value="#212121"/>
            <Setter Property="Padding"                      Value="8"/>
            <Setter Property="BorderBrush"                  Value="#B0B8C4"/>
            <Setter Property="BorderThickness"              Value="1"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Border x:Name="Bd" Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="4" SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
                        </Border>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsFocused" Value="True">
                                <Setter TargetName="Bd" Property="BorderBrush"     Value="#1976D2"/>
                                <Setter TargetName="Bd" Property="BorderThickness" Value="1.5"/>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- Code editor TextBox (multiline, read-only output) -->
        <Style x:Key="CodeOutputStyle" TargetType="TextBox">
            <Setter Property="AcceptsReturn"                Value="True"/>
            <Setter Property="FontFamily"                   Value="Consolas"/>
            <Setter Property="FontSize"                     Value="12"/>
            <Setter Property="TextWrapping"                 Value="NoWrap"/>
            <Setter Property="VerticalScrollBarVisibility"  Value="Auto"/>
            <Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
            <Setter Property="VerticalContentAlignment"     Value="Top"/>
            <Setter Property="IsReadOnly"                   Value="True"/>
            <Setter Property="Background"                   Value="#F6F8FA"/>
            <Setter Property="Foreground"                   Value="#212121"/>
            <Setter Property="Padding"                      Value="8"/>
            <Setter Property="BorderBrush"                  Value="#D0D7DE"/>
            <Setter Property="BorderThickness"              Value="1"/>
            <Setter Property="Cursor"                       Value="Arrow"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TextBox">
                        <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                CornerRadius="4" SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost" Margin="{TemplateBinding Padding}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- TabControl (flat) -->
        <Style TargetType="TabControl">
            <Setter Property="Background"       Value="Transparent"/>
            <Setter Property="BorderThickness"  Value="0"/>
            <Setter Property="Padding"          Value="0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TabControl">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                            </Grid.RowDefinitions>
                            <Border Grid.Row="0" Background="White"
                                    BorderThickness="0,0,0,1" BorderBrush="#E0E0E0">
                                <TabPanel x:Name="HeaderPanel" IsItemsHost="True" Margin="8,0"/>
                            </Border>
                            <ContentPresenter Grid.Row="1" x:Name="PART_SelectedContentHost"
                                              ContentSource="SelectedContent"/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <!-- TabItem (flat with underline) -->
        <Style TargetType="TabItem">
            <Setter Property="Foreground"      Value="#757575"/>
            <Setter Property="Background"      Value="Transparent"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="TabItem">
                        <Grid x:Name="Root" Cursor="Hand">
                            <Border x:Name="Bd" Background="Transparent" Padding="20,12">
                                <TextBlock x:Name="Txt" Text="{TemplateBinding Header}"
                                           FontSize="13" Foreground="{TemplateBinding Foreground}"/>
                            </Border>
                            <Border x:Name="Bar" Height="2.5" VerticalAlignment="Bottom"
                                    Background="#1976D2" Visibility="Collapsed"/>
                        </Grid>
                        <ControlTemplate.Triggers>
                            <Trigger Property="IsSelected" Value="True">
                                <Setter TargetName="Bar" Property="Visibility"  Value="Visible"/>
                                <Setter TargetName="Txt" Property="Foreground"  Value="#1976D2"/>
                                <Setter TargetName="Txt" Property="FontWeight"  Value="SemiBold"/>
                            </Trigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="IsMouseOver" Value="True"/>
                                    <Condition Property="IsSelected"  Value="False"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="Bd" Property="Background" Value="#F5F5F5"/>
                            </MultiTrigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <TabControl>
        <!-- ===== Tab 1: ファイル改行 ===== -->
        <TabItem Header="ファイル改行">
            <ScrollViewer VerticalScrollBarVisibility="Auto" Background="#F0F2F5">
                <StackPanel Margin="16,16,16,8">

                    <!-- 入力ファイル -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,14">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="入力ファイル" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <Grid Margin="0,0,0,8">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="ファイル" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbLineBreakInput"/>
                                <Button    Grid.Column="2" Content="参照..." Margin="8,0,0,0"
                                           Style="{StaticResource BrowseBtn}"
                                           Click="BtnLineBreakInputBrowse_Click"/>
                            </Grid>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="文字コード" Foreground="#555" VerticalAlignment="Center"/>
                                <ComboBox  Grid.Column="1" x:Name="CbLineBreakEncoding" Width="250"/>
                            </Grid>
                        </StackPanel>
                    </Border>

                    <!-- 改行設定 -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,14">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="改行設定" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Text="バイト数" Foreground="#555" VerticalAlignment="Center" Width="100"/>
                                <TextBox   x:Name="TbByteCount" Width="90" Text="80"/>
                                <TextBlock Text="バイトごとに改行を挿入" Foreground="#555"
                                           VerticalAlignment="Center" Margin="12,0,0,0"/>
                            </StackPanel>
                        </StackPanel>
                    </Border>

                    <!-- 出力設定 -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,14">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="出力設定" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <Grid Margin="0,0,0,10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="出力フォルダ" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbLineBreakOutput"/>
                                <Button    Grid.Column="2" Content="参照..." Margin="8,0,0,0"
                                           Style="{StaticResource BrowseBtn}"
                                           Click="BtnLineBreakOutputBrowse_Click"/>
                            </Grid>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
                                <RadioButton x:Name="RbSameFolder" Content="同じフォルダに出力"
                                             GroupName="OutputMode" IsChecked="True"
                                             Checked="RbOutputMode_Checked"/>
                                <RadioButton x:Name="RbSpecifyFolder" Content="フォルダを指定"
                                             GroupName="OutputMode" Margin="20,0,0,0"
                                             Checked="RbOutputMode_Checked"/>
                            </StackPanel>
                            <StackPanel Orientation="Horizontal">
                                <CheckBox  x:Name="ChkAddExtension" Content="拡張子を付加する"/>
                                <TextBlock Text="拡張子 :" Foreground="#555" VerticalAlignment="Center" Margin="14,0,8,0"/>
                                <TextBox   x:Name="TbAddExtension" Width="100" Text=".out"
                                           IsEnabled="{Binding IsChecked, ElementName=ChkAddExtension}"/>
                            </StackPanel>
                        </StackPanel>
                    </Border>

                    <!-- 実行ボタン -->
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2,0,2,12">
                        <Button Content="実行" Style="{StaticResource ExecBtn}"
                                Click="BtnLineBreakExecute_Click"/>
                    </StackPanel>

                    <!-- ログ -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="ログ" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <TextBox x:Name="TbLineBreakLog" Height="150" Style="{StaticResource LogStyle}"/>
                        </StackPanel>
                    </Border>

                </StackPanel>
            </ScrollViewer>
        </TabItem>

        <!-- ===== Tab 2: フォルダ内文字置き換え ===== -->
        <TabItem Header="フォルダ内文字置き換え">
            <ScrollViewer VerticalScrollBarVisibility="Auto" Background="#F0F2F5">
                <StackPanel Margin="16,16,16,8">

                    <!-- フォルダ設定 -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,14">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="フォルダ設定" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <Grid Margin="0,0,0,8">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="対象フォルダ" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbReplaceSourceFolder"/>
                                <Button    Grid.Column="2" Content="参照..." Margin="8,0,0,0"
                                           Style="{StaticResource BrowseBtn}"
                                           Click="BtnReplaceSourceBrowse_Click"/>
                            </Grid>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="出力フォルダ" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbReplaceDestFolder"/>
                                <Button    Grid.Column="2" Content="参照..." Margin="8,0,0,0"
                                           Style="{StaticResource BrowseBtn}"
                                           Click="BtnReplaceDestBrowse_Click"/>
                            </Grid>
                        </StackPanel>
                    </Border>

                    <!-- 置き換え設定 -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,14">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="置き換え設定" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <Grid Margin="0,0,0,10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="置き換え前" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbReplaceFrom" Margin="0,0,10,0"/>
                                <TextBlock Grid.Column="2" Text="置き換え後" Foreground="#555" VerticalAlignment="Center" Margin="8,0,0,0"/>
                                <TextBox   Grid.Column="3" x:Name="TbReplaceTo"/>
                            </Grid>
                            <Grid Margin="0,0,0,12">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="Auto"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="文字コード" Foreground="#555" VerticalAlignment="Center"/>
                                <ComboBox  Grid.Column="1" x:Name="CbReplaceEncoding" Width="250"/>
                            </Grid>
                            <StackPanel Orientation="Horizontal">
                                <CheckBox x:Name="ChkReplaceCaseSensitive" Content="大文字/小文字を区別する" IsChecked="True"/>
                                <CheckBox x:Name="ChkReplaceFileName" Content="ファイル名も置き換える"
                                          IsChecked="True" Margin="24,0,0,0"/>
                            </StackPanel>
                        </StackPanel>
                    </Border>

                    <!-- 除外設定 -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,14">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="除外設定" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <Grid Margin="0,0,0,3">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="除外フォルダ" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbExcludeFolders"/>
                            </Grid>
                            <TextBlock Text="区切り文字 : ;(セミコロン) 例: .git;node_modules;bin"
                                       Foreground="#9E9E9E" FontSize="11" Margin="100,3,0,12"/>
                            <Grid Margin="0,0,0,3">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="100"/>
                                    <ColumnDefinition Width="*"/>
                                </Grid.ColumnDefinitions>
                                <TextBlock Grid.Column="0" Text="除外拡張子" Foreground="#555" VerticalAlignment="Center"/>
                                <TextBox   Grid.Column="1" x:Name="TbExcludeExtensions"/>
                            </Grid>
                            <TextBlock Text="区切り文字 : ;(セミコロン) 例: .exe;.dll;.png"
                                       Foreground="#9E9E9E" FontSize="11" Margin="100,3,0,0"/>
                        </StackPanel>
                    </Border>

                    <!-- 実行ボタン群 -->
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="2,0,2,12">
                        <Button Content="ドライラン(確認のみ)" Style="{StaticResource DryRunBtn}"
                                Margin="0,0,10,0" Click="BtnReplaceDryRun_Click"/>
                        <Button Content="実行" Style="{StaticResource ExecBtn}"
                                Click="BtnReplaceExecute_Click"/>
                    </StackPanel>

                    <!-- ログ -->
                    <Border Style="{StaticResource CardStyle}">
                        <StackPanel>
                            <StackPanel Orientation="Horizontal" Margin="0,0,0,10">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="ログ" FontWeight="SemiBold" FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <TextBox x:Name="TbReplaceLog" Height="190" Style="{StaticResource LogStyle}"/>
                        </StackPanel>
                    </Border>

                </StackPanel>
            </ScrollViewer>
        </TabItem>
        <!-- ===== Tab 3: タブ区切り→JSON変換 ===== -->
        <TabItem Header="タブ区切り→JSON変換">
            <Grid Background="#F0F2F5">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="*"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>

                <!-- オプション行 -->
                <Border Grid.Row="0" Style="{StaticResource CardStyle}" Margin="16,16,16,8">
                    <WrapPanel Orientation="Horizontal">
                        <StackPanel Orientation="Horizontal" Margin="0,0,24,0">
                            <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                            <TextBlock Text="文字コード" FontWeight="SemiBold" Foreground="#212121"
                                       VerticalAlignment="Center" Margin="0,0,10,0"/>
                            <ComboBox x:Name="CbJsonEncoding" Width="210"
                                      SelectionChanged="CbJsonEncoding_SelectionChanged"/>
                        </StackPanel>
                        <Rectangle Width="1" Fill="#E0E0E0" Margin="0,0,24,0"/>
                        <StackPanel Orientation="Horizontal" Margin="0,0,10,0">
                            <Border Width="3" CornerRadius="2" Background="#388E3C" Margin="0,1,10,1"/>
                            <TextBlock Text="出力オプション" FontWeight="SemiBold" Foreground="#212121"
                                       VerticalAlignment="Center" Margin="0,0,14,0"/>
                        </StackPanel>
                        <CheckBox x:Name="ChkJsonPrettyPrint" Content="整形出力"   IsChecked="True" Margin="0,0,18,0"/>
                        <CheckBox x:Name="ChkJsonArrayWrap"   Content="配列形式"   IsChecked="True" Margin="0,0,18,0"/>
                        <CheckBox x:Name="ChkJsonOmitNull"    Content="null省略"/>
                    </WrapPanel>
                </Border>

                <!-- 左右分割エリア -->
                <Grid Grid.Row="1" Margin="16,0,16,8">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>

                    <!-- 入力パネル -->
                    <Border Grid.Column="0" Style="{StaticResource CardStyle}" Margin="2,2,4,2">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
                                <Border Width="3" CornerRadius="2" Background="#1976D2" Margin="0,1,10,1"/>
                                <TextBlock Text="入力(タブ区切りテキスト)" FontWeight="SemiBold"
                                           FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <TextBox Grid.Row="1" x:Name="TbJsonInput"
                                     Style="{StaticResource CodeInputStyle}"
                                     TextChanged="TbJsonInput_TextChanged"
                                     AllowDrop="True"
                                     Drop="TbJsonInput_Drop"
                                     PreviewDragOver="TbJsonInput_PreviewDragOver"/>
                            <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,8,0,0">
                                <TextBlock x:Name="TxtJsonInputStatus" FontSize="11"
                                           Foreground="#9E9E9E" VerticalAlignment="Center"/>
                                <Button Content="ファイルを開く" Margin="8,0,0,0"
                                        Style="{StaticResource BrowseBtn}"
                                        Click="BtnJsonOpenFile_Click"/>
                                <Button Content="貼り付け" Margin="6,0,0,0"
                                        Style="{StaticResource BrowseBtn}"
                                        Click="BtnJsonPaste_Click"/>
                                <Button Content="クリア" Margin="6,0,0,0"
                                        Style="{StaticResource BrowseBtn}"
                                        Click="BtnJsonClear_Click"/>
                            </StackPanel>
                        </Grid>
                    </Border>

                    <!-- 変換ボタン -->
                    <StackPanel Grid.Column="1" VerticalAlignment="Center" Margin="4,0">
                        <Button Content="変換 →" Style="{StaticResource ExecBtn}"
                                Click="BtnJsonConvert_Click"/>
                    </StackPanel>

                    <!-- 出力パネル -->
                    <Border Grid.Column="2" Style="{StaticResource CardStyle}" Margin="4,2,2,2">
                        <Grid>
                            <Grid.RowDefinitions>
                                <RowDefinition Height="Auto"/>
                                <RowDefinition Height="*"/>
                                <RowDefinition Height="Auto"/>
                            </Grid.RowDefinitions>
                            <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,10">
                                <Border Width="3" CornerRadius="2" Background="#388E3C" Margin="0,1,10,1"/>
                                <TextBlock Text="出力(JSON)" FontWeight="SemiBold"
                                           FontSize="13" Foreground="#212121"/>
                            </StackPanel>
                            <TextBox Grid.Row="1" x:Name="TbJsonOutput"
                                     Style="{StaticResource CodeOutputStyle}"/>
                            <StackPanel Grid.Row="2" Orientation="Horizontal" Margin="0,8,0,0">
                                <TextBlock x:Name="TxtJsonOutputStatus" FontSize="11"
                                           Foreground="#9E9E9E" VerticalAlignment="Center"/>
                                <Button Content="コピー" Margin="8,0,0,0"
                                        Style="{StaticResource BrowseBtn}"
                                        Click="BtnJsonCopy_Click"/>
                                <Button Content="保存..." Margin="6,0,0,0"
                                        Style="{StaticResource BrowseBtn}"
                                        Click="BtnJsonSave_Click"/>
                            </StackPanel>
                        </Grid>
                    </Border>
                </Grid>

                <!-- エラー表示 -->
                <TextBlock Grid.Row="2" x:Name="TxtJsonError"
                           Foreground="#C62828" FontSize="12" TextWrapping="Wrap"
                           Margin="18,0,18,10" Visibility="Collapsed"/>
            </Grid>
        </TabItem>
    </TabControl>
</Window>
using Microsoft.Win32;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Windows;
using System.Windows.Controls;

namespace BenriTool;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        InitializeEncodings();
        InitJsonEncodings();
        RbOutputMode_Checked(null, null);
    }

    // ---------------------------------------------------------------
    // 共通
    // ---------------------------------------------------------------
    private static readonly (string Label, string Name)[] Encodings =
    [
        ("UTF-8", "utf-8"),
        ("UTF-8 BOM付き", "utf-8-bom"),
        ("Shift_JIS", "shift_jis"),
        ("EUC-JP", "euc-jp"),
        ("UTF-16 LE", "utf-16"),
        ("UTF-16 BE", "utf-16BE"),
        ("ASCII", "us-ascii"),
        ("EBCDIC (US-Canada / CP037)", "ibm037"),
        ("EBCDIC (International / CP500)", "ibm500"),
        ("EBCDIC (日本語カタカナ / CP930)", "ibm930"),
        ("EBCDIC (日本語英数字 / CP939)", "ibm939"),
    ];

    private void InitializeEncodings()
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

        foreach (var (label, _) in Encodings)
        {
            CbLineBreakEncoding.Items.Add(label);
            CbReplaceEncoding.Items.Add(label);
        }
        CbLineBreakEncoding.SelectedIndex = 0;
        CbReplaceEncoding.SelectedIndex = 0;
    }

    private Encoding GetEncoding(ComboBox cb, bool useBom = false)
    {
        var name = Encodings[cb.SelectedIndex].Name;
        if (name == "utf-8-bom") return new UTF8Encoding(true);
        if (name == "utf-8") return new UTF8Encoding(false);
        return Encoding.GetEncoding(name);
    }

    private static string BrowseFolder(string title)
    {
        var dialog = new OpenFolderDialog { Title = title };
        return dialog.ShowDialog() == true ? dialog.FolderName : string.Empty;
    }

    // ---------------------------------------------------------------
    // Tab1: ファイル改行
    // ---------------------------------------------------------------
    private void BtnLineBreakInputBrowse_Click(object sender, RoutedEventArgs e)
    {
        var dialog = new OpenFileDialog { Title = "入力ファイルを選択", Filter = "すべてのファイル (*.*)|*.*" };
        if (dialog.ShowDialog() == true)
            TbLineBreakInput.Text = dialog.FileName;
    }

    private void BtnLineBreakOutputBrowse_Click(object sender, RoutedEventArgs e)
    {
        var folder = BrowseFolder("出力フォルダを選択");
        if (!string.IsNullOrEmpty(folder))
            TbLineBreakOutput.Text = folder;
    }

    private void RbOutputMode_Checked(object? sender, RoutedEventArgs? e)
    {
        if (TbLineBreakOutput == null) return;
        TbLineBreakOutput.IsEnabled = RbSpecifyFolder?.IsChecked == true;
    }

    private void BtnLineBreakExecute_Click(object sender, RoutedEventArgs e)
    {
        TbLineBreakLog.Clear();
        try
        {
            var inputPath = TbLineBreakInput.Text.Trim();
            if (!File.Exists(inputPath))
            {
                Log(TbLineBreakLog, "エラー: 入力ファイルが存在しません。");
                return;
            }

            if (!int.TryParse(TbByteCount.Text, out var byteCount) || byteCount <= 0)
            {
                Log(TbLineBreakLog, "エラー: バイト数に正の整数を入力してください。");
                return;
            }

            var enc = GetEncoding(CbLineBreakEncoding);

            string outputDir;
            if (RbSameFolder.IsChecked == true)
                outputDir = Path.GetDirectoryName(inputPath)!;
            else
            {
                outputDir = TbLineBreakOutput.Text.Trim();
                if (string.IsNullOrEmpty(outputDir))
                {
                    Log(TbLineBreakLog, "エラー: 出力フォルダを指定してください。");
                    return;
                }
                Directory.CreateDirectory(outputDir);
            }

            var fileName = Path.GetFileName(inputPath);
            if (ChkAddExtension.IsChecked == true)
                fileName += TbAddExtension.Text;

            var outputPath = Path.Combine(outputDir, fileName);

            var content = File.ReadAllBytes(inputPath);
            using var writer = new BinaryWriter(File.Open(outputPath, FileMode.Create));
            var newline = enc.GetBytes(Environment.NewLine);

            int pos = 0;
            while (pos < content.Length)
            {
                int len = Math.Min(byteCount, content.Length - pos);
                writer.Write(content, pos, len);
                pos += len;
                if (pos < content.Length)
                    writer.Write(newline);
            }

            Log(TbLineBreakLog, $"完了: {outputPath}");
            Log(TbLineBreakLog, $"入力: {content.Length} バイト → {byteCount} バイトごとに改行を挿入");
        }
        catch (Exception ex)
        {
            Log(TbLineBreakLog, $"エラー: {ex.Message}");
        }
    }

    // ---------------------------------------------------------------
    // Tab2: フォルダ内文字置き換え
    // ---------------------------------------------------------------
    private void BtnReplaceSourceBrowse_Click(object sender, RoutedEventArgs e)
    {
        var folder = BrowseFolder("対象フォルダを選択");
        if (!string.IsNullOrEmpty(folder))
            TbReplaceSourceFolder.Text = folder;
    }

    private void BtnReplaceDestBrowse_Click(object sender, RoutedEventArgs e)
    {
        var folder = BrowseFolder("出力フォルダを選択");
        if (!string.IsNullOrEmpty(folder))
            TbReplaceDestFolder.Text = folder;
    }

    private void BtnReplaceDryRun_Click(object sender, RoutedEventArgs e) =>
        ExecuteReplace(dryRun: true);

    private void BtnReplaceExecute_Click(object sender, RoutedEventArgs e) =>
        ExecuteReplace(dryRun: false);

    private void ExecuteReplace(bool dryRun)
    {
        TbReplaceLog.Clear();
        Log(TbReplaceLog, dryRun ? "=== ドライラン開始 ===" : "=== 実行開始 ===");

        var srcRoot = TbReplaceSourceFolder.Text.Trim();
        if (!Directory.Exists(srcRoot))
        {
            Log(TbReplaceLog, "エラー: 対象フォルダが存在しません。");
            return;
        }

        var destRoot = TbReplaceDestFolder.Text.Trim();
        if (string.IsNullOrEmpty(destRoot))
        {
            Log(TbReplaceLog, "エラー: 出力フォルダを指定してください。");
            return;
        }

        var from = TbReplaceFrom.Text;
        if (string.IsNullOrEmpty(from))
        {
            Log(TbReplaceLog, "エラー: 置き換え前の文字を入力してください。");
            return;
        }
        var to = TbReplaceTo.Text;

        var comparison = ChkReplaceCaseSensitive.IsChecked == true
            ? StringComparison.Ordinal
            : StringComparison.OrdinalIgnoreCase;

        var excludeFolders = TbExcludeFolders.Text
            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        var excludeExts = TbExcludeExtensions.Text
            .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Select(x => x.StartsWith('.') ? x.ToLower() : "." + x.ToLower())
            .ToHashSet();

        var replaceFileName = ChkReplaceFileName.IsChecked == true;
        var enc = GetEncoding(CbReplaceEncoding);

        int fileCount = 0, replacedCount = 0;

        try
        {
            if (!dryRun) Directory.CreateDirectory(destRoot);

            ProcessDirectory(srcRoot, srcRoot, destRoot, from, to, comparison,
                excludeFolders, excludeExts, replaceFileName, enc, dryRun,
                ref fileCount, ref replacedCount);

            Log(TbReplaceLog, $"");
            Log(TbReplaceLog, $"処理ファイル: {fileCount} 件 置き換えあり: {replacedCount} 件");
            Log(TbReplaceLog, dryRun ? "=== ドライラン完了 ===" : "=== 完了 ===");
        }
        catch (Exception ex)
        {
            Log(TbReplaceLog, $"エラー: {ex.Message}");
        }
    }

    private void ProcessDirectory(
        string srcRoot, string srcDir, string destRoot,
        string from, string to, StringComparison comparison,
        string[] excludeFolders, HashSet<string> excludeExts,
        bool replaceFileName, Encoding enc, bool dryRun,
        ref int fileCount, ref int replacedCount)
    {
        var relDir = Path.GetRelativePath(srcRoot, srcDir);
        var dirName = Path.GetFileName(srcDir);

        if (excludeFolders.Any(ef => dirName.Equals(ef, StringComparison.OrdinalIgnoreCase)))
        {
            Log(TbReplaceLog, $"[除外フォルダ] {relDir}");
            return;
        }

        var destDirName = replaceFileName
            ? ReplaceString(dirName, from, to, comparison)
            : dirName;
        var destDir = srcDir == srcRoot
            ? destRoot
            : Path.Combine(destRoot, ReplacePathSegments(
                Path.GetRelativePath(srcRoot, srcDir), from, to, comparison, replaceFileName));

        if (!dryRun) Directory.CreateDirectory(destDir);

        foreach (var filePath in Directory.GetFiles(srcDir))
        {
            var ext = Path.GetExtension(filePath).ToLower();
            if (excludeExts.Contains(ext))
            {
                Log(TbReplaceLog, $"[除外拡張子] {Path.GetRelativePath(srcRoot, filePath)}");
                continue;
            }

            fileCount++;
            var origFileName = Path.GetFileName(filePath);
            var newFileName = replaceFileName
                ? ReplaceString(origFileName, from, to, comparison)
                : origFileName;

            bool contentReplaced = false;
            string? newContent = null;

            try
            {
                var content = File.ReadAllText(filePath, enc);
                newContent = ReplaceString(content, from, to, comparison);
                contentReplaced = !string.Equals(content, newContent, StringComparison.Ordinal);
            }
            catch
            {
                // バイナリファイルなどは内容置き換えをスキップ
                newContent = null;
            }

            var fileNameChanged = !string.Equals(origFileName, newFileName, StringComparison.Ordinal);
            if (contentReplaced || fileNameChanged)
            {
                replacedCount++;
                var action = dryRun ? "[ドライ]" : "[置換]";
                Log(TbReplaceLog, $"{action} {Path.GetRelativePath(srcRoot, filePath)}" +
                    (fileNameChanged ? $" → {newFileName}" : ""));
            }
            else
            {
                Log(TbReplaceLog, $"[コピー] {Path.GetRelativePath(srcRoot, filePath)}");
            }

            if (!dryRun)
            {
                var destFile = Path.Combine(destDir, newFileName);
                if (newContent != null)
                    File.WriteAllText(destFile, newContent, enc);
                else
                    File.Copy(filePath, destFile, overwrite: true);
            }
        }

        foreach (var subDir in Directory.GetDirectories(srcDir))
        {
            ProcessDirectory(srcRoot, subDir, destRoot, from, to, comparison,
                excludeFolders, excludeExts, replaceFileName, enc, dryRun,
                ref fileCount, ref replacedCount);
        }
    }

    private static string ReplaceString(string src, string from, string to, StringComparison comparison)
    {
        if (!src.Contains(from, comparison)) return src;
        var sb = new StringBuilder();
        int index = 0;
        while (index < src.Length)
        {
            int found = src.IndexOf(from, index, comparison);
            if (found < 0)
            {
                sb.Append(src, index, src.Length - index);
                break;
            }
            sb.Append(src, index, found - index);
            sb.Append(to);
            index = found + from.Length;
        }
        return sb.ToString();
    }

    private static string ReplacePathSegments(string relPath, string from, string to,
        StringComparison comparison, bool replace)
    {
        if (!replace) return relPath;
        var parts = relPath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        return string.Join(Path.DirectorySeparatorChar,
            parts.Select(p => ReplaceString(p, from, to, comparison)));
    }

    private void Log(TextBox tb, string message)
    {
        tb.AppendText(message + Environment.NewLine);
        tb.ScrollToEnd();
    }

    // ---------------------------------------------------------------
    // Tab3: タブ区切り→JSON変換
    // ---------------------------------------------------------------
    private static readonly (string Label, int CodePage)[] JsonEncodings =
    [
        ("自動判別",            0),
        ("UTF-8",               65001),
        ("UTF-8 BOM付き",       -1),
        ("Shift-JIS (CP932)",   932),
        ("EUC-JP",              51932),
        ("UTF-16 LE",           1200),
        ("UTF-16 BE",           1201),
    ];

    private string? _jsonLastLoadedPath;

    private void InitJsonEncodings()
    {
        foreach (var (label, _) in JsonEncodings)
            CbJsonEncoding.Items.Add(label);
        CbJsonEncoding.SelectedIndex = 0;
    }

    private bool IsJsonAutoDetect => JsonEncodings[CbJsonEncoding.SelectedIndex].CodePage == 0;

    private Encoding GetJsonEncoding()
    {
        var (_, codePage) = JsonEncodings[CbJsonEncoding.SelectedIndex];
        return codePage switch
        {
            0  => Encoding.UTF8,
            -1 => new UTF8Encoding(true),
            _  => Encoding.GetEncoding(codePage)
        };
    }

    private void CbJsonEncoding_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        if (_jsonLastLoadedPath is not null)
            JsonLoadFile(_jsonLastLoadedPath);
    }

    private void TbJsonInput_TextChanged(object sender, TextChangedEventArgs e)
    {
        JsonUpdateInputStatus();
        TxtJsonError.Visibility = Visibility.Collapsed;
    }

    private void JsonUpdateInputStatus()
    {
        var lines = TbJsonInput.Text.Split('\n', StringSplitOptions.None);
        var nonEmpty = lines.Count(l => l.Trim().Length > 0);
        int dataRows = nonEmpty > 1 ? nonEmpty - 1 : 0;
        TxtJsonInputStatus.Text = $"{nonEmpty} 行 (データ: {dataRows} 件)";
    }

    private void BtnJsonOpenFile_Click(object sender, RoutedEventArgs e)
    {
        var dialog = new OpenFileDialog
        {
            Title = "タブ区切りファイルを開く",
            Filter = "テキストファイル (*.txt;*.tsv;*.tab;*.csv)|*.txt;*.tsv;*.tab;*.csv|すべてのファイル (*.*)|*.*"
        };
        if (dialog.ShowDialog() == true)
            JsonLoadFile(dialog.FileName);
    }

    private void TbJsonInput_PreviewDragOver(object sender, DragEventArgs e)
    {
        e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
            ? DragDropEffects.Copy
            : DragDropEffects.None;
        e.Handled = true;
    }

    private void TbJsonInput_Drop(object sender, DragEventArgs e)
    {
        if (e.Data.GetData(DataFormats.FileDrop) is string[] files && files.Length > 0)
            JsonLoadFile(files[0]);
    }

    private void JsonLoadFile(string path)
    {
        try
        {
            var encoding = IsJsonAutoDetect ? JsonDetectEncoding(path) : GetJsonEncoding();
            TbJsonInput.Text = File.ReadAllText(path, encoding);
            _jsonLastLoadedPath = path;
            JsonUpdateInputStatus();
            TxtJsonInputStatus.Text += $"  [{Path.GetFileName(path)} / {encoding.WebName}]";
            TxtJsonError.Visibility = Visibility.Collapsed;
        }
        catch (Exception ex)
        {
            JsonShowError($"ファイル読み込みエラー: {ex.Message}");
        }
    }

    private static Encoding JsonDetectEncoding(string path)
    {
        using var fs = File.OpenRead(path);
        var bom = new byte[4];
        _ = fs.Read(bom, 0, 4);
        if (bom[0] == 0xEF && bom[1] == 0xBB && bom[2] == 0xBF) return Encoding.UTF8;
        if (bom[0] == 0xFF && bom[1] == 0xFE) return Encoding.Unicode;
        if (bom[0] == 0xFE && bom[1] == 0xFF) return Encoding.BigEndianUnicode;
        try
        {
            var bytes = File.ReadAllBytes(path);
            var utf8 = new UTF8Encoding(false, throwOnInvalidBytes: true);
            utf8.GetString(bytes);
            return utf8;
        }
        catch
        {
            return Encoding.GetEncoding(932);
        }
    }

    private void BtnJsonPaste_Click(object sender, RoutedEventArgs e)
    {
        if (!Clipboard.ContainsText()) return;
        TbJsonInput.Text = Clipboard.GetText();
        _jsonLastLoadedPath = null;
        JsonUpdateInputStatus();
    }

    private void BtnJsonClear_Click(object sender, RoutedEventArgs e)
    {
        TbJsonInput.Text = string.Empty;
        TbJsonOutput.Text = string.Empty;
        TxtJsonInputStatus.Text = string.Empty;
        TxtJsonOutputStatus.Text = string.Empty;
        _jsonLastLoadedPath = null;
        TxtJsonError.Visibility = Visibility.Collapsed;
    }

    private void BtnJsonConvert_Click(object sender, RoutedEventArgs e)
    {
        TxtJsonError.Visibility = Visibility.Collapsed;
        TbJsonOutput.Text = string.Empty;

        var input = TbJsonInput.Text;
        if (string.IsNullOrWhiteSpace(input))
        {
            JsonShowError("入力が空です。タブ区切りテキストを入力してください。");
            return;
        }

        try
        {
            var json = ConvertTabToJson(input,
                prettyPrint: ChkJsonPrettyPrint.IsChecked == true,
                asArray:     ChkJsonArrayWrap.IsChecked == true,
                omitNull:    ChkJsonOmitNull.IsChecked == true);
            TbJsonOutput.Text = json;
            TxtJsonOutputStatus.Text = $"{json.Length} 文字";
        }
        catch (Exception ex)
        {
            JsonShowError($"変換エラー: {ex.Message}");
        }
    }

    private static string ConvertTabToJson(string input, bool prettyPrint, bool asArray, bool omitNull)
    {
        var lines = input.Split('\n')
                         .Select(l => l.TrimEnd('\r'))
                         .ToArray();

        var nonEmptyLines = lines
            .Where(l => l.Length > 0)
            .ToList();

        if (nonEmptyLines.Count == 0)
            throw new InvalidOperationException("有効な行がありません。");

        var headers = nonEmptyLines[0].Split('\t');
        if (headers.Length == 0)
            throw new InvalidOperationException("ヘッダー行にタブ区切りデータが見つかりません。");

        var options = new JsonWriterOptions
        {
            Indented = prettyPrint,
            Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
        };

        using var stream = new MemoryStream();
        using var writer = new Utf8JsonWriter(stream, options);

        if (asArray) writer.WriteStartArray();

        foreach (var line in nonEmptyLines.Skip(1))
        {
            var values = line.Split('\t');
            writer.WriteStartObject();

            for (int i = 0; i < headers.Length; i++)
            {
                var key = headers[i].Trim();
                if (string.IsNullOrEmpty(key)) key = $"field{i + 1}";

                var value = i < values.Length ? values[i] : null;

                if (string.IsNullOrEmpty(value))
                {
                    if (omitNull) continue;
                    writer.WriteNull(key);
                    continue;
                }

                writer.WritePropertyName(key);
                if (long.TryParse(value, out var longVal))
                    writer.WriteNumberValue(longVal);
                else if (double.TryParse(value,
                         System.Globalization.NumberStyles.Any,
                         System.Globalization.CultureInfo.InvariantCulture, out var dblVal))
                    writer.WriteNumberValue(dblVal);
                else if (bool.TryParse(value, out var boolVal))
                    writer.WriteBooleanValue(boolVal);
                else
                    writer.WriteStringValue(value);
            }

            writer.WriteEndObject();
        }

        if (asArray) writer.WriteEndArray();
        writer.Flush();
        return Encoding.UTF8.GetString(stream.ToArray());
    }

    private void BtnJsonCopy_Click(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrEmpty(TbJsonOutput.Text)) return;
        Clipboard.SetText(TbJsonOutput.Text);
        TxtJsonOutputStatus.Text = "コピーしました";
    }

    private void BtnJsonSave_Click(object sender, RoutedEventArgs e)
    {
        if (string.IsNullOrEmpty(TbJsonOutput.Text))
        {
            JsonShowError("保存するJSONがありません。先に変換を実行してください。");
            return;
        }

        var dialog = new SaveFileDialog
        {
            Title    = "JSONファイルを保存",
            Filter   = "JSON ファイル (*.json)|*.json|すべてのファイル (*.*)|*.*",
            DefaultExt = "json",
            FileName = "output.json"
        };

        if (dialog.ShowDialog() == true)
        {
            File.WriteAllText(dialog.FileName, TbJsonOutput.Text, Encoding.UTF8);
            TxtJsonOutputStatus.Text = $"保存: {Path.GetFileName(dialog.FileName)}";
        }
    }

    private void JsonShowError(string message)
    {
        TxtJsonError.Text = message;
        TxtJsonError.Visibility = Visibility.Visible;
    }
}