Documentation
use anyhow::Result;
use comfy_table::{Table, presets::UTF8_FULL};
use dialoguer::{Confirm, Input, Select};
use inflector::Inflector;
use qw::{FieldDef, generate_add_column_sql, generate_migration_sql, write_migration};
use sercli::Migrations;
use serde::Deserialize;
use structopt::StructOpt;

#[derive(StructOpt, Debug)]
enum Args {
    New { name: String },
    Model(ModelArgs),
}

#[derive(StructOpt, Debug)]
enum ModelArgs {
    Show,
    Add,
    Edit,
}

#[derive(Deserialize)]
struct Config {
    migrations: String,
}

fn find_config() -> Config {
    let mut dir = std::env::current_dir().unwrap();
    loop {
        let candidate = dir.join("qw.toml");
        if candidate.exists() {
            let contents = std::fs::read_to_string(&candidate).unwrap();
            return toml::from_str(&contents).unwrap();
        }
        assert!(dir.pop(), "qw.toml not found");
    }
}

const SQL_TYPES: &[&str] = &[
    "bigint",
    "integer",
    "smallint",
    "varchar",
    "boolean",
    "decimal",
    "timestamp",
    "real",
];

fn add_model(migrations_path: &str) -> Result<()> {
    let model_name: String = Input::new().with_prompt("Model name").interact_text()?;

    let default_pk = Confirm::new()
        .with_prompt("Add default PK: id bigint NOT NULL PRIMARY KEY")
        .default(true)
        .interact()?;

    let mut fields = vec![];

    loop {
        let name: String = Input::new()
            .with_prompt("Field name (empty to finish)")
            .allow_empty(true)
            .interact_text()?;

        if name.is_empty() {
            break;
        }

        let type_idx = Select::new().with_prompt("Type").items(SQL_TYPES).default(0).interact()?;
        let not_null = Confirm::new().with_prompt("NOT NULL").default(true).interact()?;

        fields.push(FieldDef {
            name,
            sql_type: SQL_TYPES[type_idx].to_string(),
            not_null,
        });
    }

    let sql = generate_migration_sql(&model_name, default_pk, &fields);
    let name = format!("add_{}", model_name.to_snake_case().to_plural());
    let filename = write_migration(migrations_path, &name, &sql)?;

    println!("Created {filename}");
    println!("{sql}");

    Ok(())
}

fn main() -> Result<()> {
    let args = Args::from_args();

    let config = find_config();

    match args {
        Args::New { name } => {
            dbg!(name);
        }
        Args::Model(ModelArgs::Show) => {
            let migrations = Migrations::get(&config.migrations)?;

            let mut table = Table::new();
            table.load_preset(UTF8_FULL);
            table.set_header(["Entity", "Table", "Fields", "Relations"]);

            for (name, entity) in &migrations.entities {
                let fields = entity
                    .fields
                    .iter()
                    .map(|f| format!("{}: {}", f.name, f.ty))
                    .collect::<Vec<_>>()
                    .join("\n");
                let relations = entity
                    .relations
                    .iter()
                    .map(|r| format!("{} -> {}", r.field, r.references))
                    .collect::<Vec<_>>()
                    .join("\n");
                table.add_row([name.as_str(), entity.table_name.as_str(), &fields, &relations]);
            }

            for (name, pg_enum) in &migrations.enums {
                table.add_row([name.as_str(), "[enum]", &pg_enum.cases.join(" | "), ""]);
            }

            println!("{table}");
        }
        Args::Model(ModelArgs::Add) => {
            add_model(&config.migrations)?;
        }
        Args::Model(ModelArgs::Edit) => {
            let migrations = Migrations::get(&config.migrations)?;
            let entity_names: Vec<&str> = migrations.entities.keys().map(String::as_str).collect();

            let idx = Select::new().with_prompt("Model").items(&entity_names).default(0).interact()?;
            let entity = &migrations.entities[entity_names[idx]];

            let field_name: String = Input::new().with_prompt("New field name").interact_text()?;
            let type_idx = Select::new().with_prompt("Type").items(SQL_TYPES).default(0).interact()?;
            let not_null = Confirm::new().with_prompt("NOT NULL").default(true).interact()?;

            let field = FieldDef {
                name: field_name,
                sql_type: SQL_TYPES[type_idx].to_string(),
                not_null,
            };
            let sql = generate_add_column_sql(&entity.table_name, &field);
            let name = format!("add_{}_to_{}", field.name.to_snake_case(), entity.table_name);
            let filename = write_migration(&config.migrations, &name, &sql)?;

            println!("Created {filename}");
            println!("{sql}");
        }
    }

    Ok(())
}