static USAGE: &str = r#"
Select columns from CSV data efficiently.

This command lets you manipulate the columns in CSV data. You can re-order,
duplicate, reverse or drop them. Columns can be referenced by index or by
name if there is a header row (duplicate column names can be disambiguated with
more indexing). Column ranges can also be specified. Finally, columns can be
selected using regular expressions.

Examples:

  # Select the first and fourth columns
  qsv select 1,4

  # Select the first 4 columns (by index)
  qsv select 1-4

  # Select the first 4 columns (by name)
  qsv select Header1-Header4

  # Ignore the first 2 columns (by range)
  qsv select 3-

  # Ignore the first 2 columns (by index)
  qsv select '!1-2'

  # Select the third column named 'Foo':
  qsv select 'Foo[2]'

  # Select the first and last columns, _ is a special character for the last column:
  qsv select 1,_

  # Reverse the order of columns:
  qsv select _-1

  # select columns starting with 'a' (regex)
  qsv select /^a/

  # select columns with a digit (regex)
  qsv select '/^.*\d.*$/'

  # remove SSN, account_no and password columns (regex)
  qsv select '!/SSN|account_no|password/'

  # Sort the columns lexicographically (i.e. by their byte values)
  qsv select 1- --sort

  # Select some columns and then sort them
  qsv select 1,4,5-7 --sort

  # Randomly shuffle the columns:
  qsv select 1- --random

  # Randomly shuffle the columns with a seed
  qsv select 1- --random --seed 42

  # Select some columns and then shuffle them with a seed:
  qsv select 1,4,5-7 --random --seed 42

  # Re-order and duplicate columns arbitrarily using different types of selectors
  qsv select 3-1,Header3-Header1,Header1,Foo[2],Header1

  # Quote column names that conflict with selector syntax:
  qsv select '\"Date - Opening\",\"Date - Actual Closing\"'

For more examples, see https://github.com/dathere/qsv/blob/master/tests/test_select.rs.
See also https://github.com/dathere/qsv/wiki/Selection-and-Inspection#select

Usage:
    qsv select [options] [--] <selection> [<input>]
    qsv select --help

select arguments:
    <selection>            The columns to select. 
                           You can select columns by index, by name, by range, by regex and
                           any combination of these. If the first character is '!', the
                           selection will be inverted. If the selection contains embedded
                           spaces or characters that conflict with selector syntax, it must
                           be quoted. See examples above.

select options:
These options only apply to the `select` command, not the `--select` option in other commands.

    -R, --random           Randomly shuffle the columns in the selection.
    --seed <number>        Seed for the random number generator.

    -S, --sort             Sort the selected columns lexicographically,
                           i.e. by their byte values.

Common options:
    -h, --help             Display this message
    -o, --output <file>    Write output to <file> instead of stdout.
    -n, --no-headers       When set, the first row will not be interpreted
                           as headers. (i.e., They are not searched, analyzed,
                           sliced, etc.)
    -d, --delimiter <arg>  The field delimiter for reading CSV data.
                           Must be a single character. (default: ,)
"#;

use rand::{SeedableRng, seq::SliceRandom};
use serde::Deserialize;

use crate::{
    CliResult,
    config::{Config, Delimiter},
    select::{SelectColumns, Selection},
    util,
};

#[derive(Deserialize)]
struct Args {
    arg_input:       Option<String>,
    arg_selection:   SelectColumns,
    flag_random:     bool,
    flag_seed:       Option<u64>,
    flag_sort:       bool,
    flag_output:     Option<String>,
    flag_no_headers: bool,
    flag_delimiter:  Option<Delimiter>,
}

pub fn run(argv: &[&str]) -> CliResult<()> {
    let args: Args = util::get_args(USAGE, argv)?;

    if args.flag_sort && args.flag_random {
        return fail_clierror!("Cannot use both --random and --sort options.");
    }

    let rconfig = Config::new(args.arg_input.as_ref())
        .delimiter(args.flag_delimiter)
        .no_headers_flag(args.flag_no_headers)
        .select(args.arg_selection);

    let mut rdr = rconfig.reader()?;
    let mut wtr = Config::new(args.flag_output.as_ref()).writer()?;

    let headers = rdr.byte_headers()?.clone();
    let sel = if args.flag_random {
        let mut rng = match args.flag_seed {
            Some(seed) => {
                // we add the DevSkim ignore comment here because we don't need to worry about
                // cryptographic security in this context.
                rand::rngs::StdRng::seed_from_u64(seed) // DevSkim: ignore DS148264
            },
            _ => rand::make_rng::<rand::rngs::StdRng>(),
        };
        let mut idxs: Vec<usize> = rconfig.selection(&headers)?.iter().copied().collect();
        idxs.shuffle(&mut rng);
        Selection::from_indices(idxs)
    } else if args.flag_sort {
        let mut idxs: Vec<usize> = rconfig.selection(&headers)?.iter().copied().collect();
        // Sort by the raw header bytes — preserves non-UTF-8 bytes and embedded
        // quotes. Use the original column index as a deterministic tiebreaker
        // so duplicate header names retain their left-to-right order.
        idxs.sort_unstable_by(|&a, &b| headers[a].cmp(&headers[b]).then_with(|| a.cmp(&b)));
        Selection::from_indices(idxs)
    } else {
        rconfig.selection(&headers)?
    };

    if !rconfig.no_headers {
        wtr.write_record(sel.iter().map(|&i| &headers[i]))?;
    }
    let mut record = csv::ByteRecord::new();
    while rdr.read_byte_record(&mut record)? {
        wtr.write_record(sel.iter().map(|&i| &record[i]))?;
    }
    wtr.flush()?;
    Ok(())
}
