# Enviable Config Converter Specification

## Overview

A Mix task that automatically converts `config/runtime.exs` from using `System.get_env/fetch_env` to using Enviable functions.

## Command

```bash
mix enviable.convert [--dry-run] [--backup]
```

### Options

- `--dry-run`: Show what would be changed without modifying files
- `--backup`: Create a backup file (`config/runtime.exs.backup`) before converting

## Scope

- **Only** converts `config/runtime.exs`
- Preserves all formatting, comments, and structure
- Adds `import Enviable` after `import Config` if not present
- Idempotent: safe to run multiple times

## Conversion Patterns

### Basic Conversions

#### Pattern 1: Direct fetch/get
```elixir
# Before
System.fetch_env!("VAR")
System.get_env("VAR")
System.get_env("VAR", "default")

# After
fetch_env!("VAR")
get_env("VAR")
get_env("VAR", "default")
```

#### Pattern 2: Integer conversion
```elixir
# Before
System.fetch_env!("PORT") |> String.to_integer()
String.to_integer(System.fetch_env!("PORT"))
System.get_env("PORT", "4000") |> String.to_integer()

# After
fetch_env_as_integer!("PORT")
fetch_env_as_integer!("PORT")
get_env_as_integer("PORT", 4000)
```

#### Pattern 3: Float conversion
```elixir
# Before
System.fetch_env!("RATE") |> String.to_float()
String.to_float(System.get_env("RATE", "1.5"))

# After
fetch_env_as_float!("RATE")
get_env_as_float("RATE", 1.5)
```

#### Pattern 4: Boolean conversion (truthy pattern)
```elixir
# Before
System.get_env("ENABLED") in ~w[1 true yes]
System.get_env("ENABLED", "false") in ~w[1 true]

# After
get_env_as_boolean("ENABLED", truthy: ["1", "true", "yes"])
get_env_as_boolean("ENABLED", truthy: ["1", "true"], default: false)
```

#### Pattern 5: Boolean conversion (equality pattern)
```elixir
# Before
System.get_env("ENABLED") == "true"
System.get_env("ENABLED", "false") == "1"

# After
get_env_as_boolean("ENABLED")
get_env_as_boolean("ENABLED", default: false)
```

#### Pattern 6: Atom conversion
```elixir
# Before
System.fetch_env!("LEVEL") |> String.to_atom()
System.fetch_env!("LEVEL") |> String.to_existing_atom()

# After
fetch_env_as_atom!("LEVEL")
fetch_env_as_safe_atom!("LEVEL")
```

#### Pattern 7: Module conversion
```elixir
# Before
Module.concat([System.fetch_env!("MODULE")])
Module.safe_concat([System.fetch_env!("MODULE")])

# After
fetch_env_as_module!("MODULE")
fetch_env_as_safe_module!("MODULE")
```

#### Pattern 8: List/split conversion
```elixir
# Before
System.get_env("ITEMS", "") |> String.split(",")
String.split(System.get_env("ITEMS", ""), ",")

# After
get_env_as_list("ITEMS", default: [])
get_env_as_list("ITEMS", default: [])
```

#### Pattern 9: JSON conversion
```elixir
# Before
System.fetch_env!("CONFIG") |> Jason.decode!()
Jason.decode!(System.fetch_env!("CONFIG"))

# After
fetch_env_as_json!("CONFIG")
fetch_env_as_json!("CONFIG")
```

### Complex Patterns

#### Pattern 10: Chained conversions
```elixir
# Before
System.get_env("PORTS", "8080,8081") |> String.split(",") |> Enum.map(&String.to_integer/1)

# After
get_env_as_list("PORTS", as: :integer, default: [8080, 8081])
```

#### Pattern 11: Case/cond with conversions
```elixir
# Before
case System.get_env("ENV") do
  "prod" -> :production
  "dev" -> :development
  _ -> :test
end

# After (no conversion - too complex)
# Leave as-is
```

## Import Handling

### Add import if missing
```elixir
# Before
import Config

config :my_app,
  port: System.fetch_env!("PORT") |> String.to_integer()

# After
import Config
import Enviable

config :my_app,
  port: fetch_env_as_integer!("PORT")
```

### Don't duplicate if present
```elixir
# Before
import Config
import Enviable

config :my_app,
  port: System.fetch_env!("PORT") |> String.to_integer()

# After
import Config
import Enviable

config :my_app,
  port: fetch_env_as_integer!("PORT")
```

## Edge Cases

### Don't convert if:
1. Inside a function definition (not at config level)
2. Part of a complex expression that can't be safely rewritten
3. Uses variables for env var names: `System.get_env(var_name)`
4. Already using Enviable functions

### Preserve:
1. All comments (inline and block)
2. Whitespace and formatting
3. Line numbers (for error reporting)
4. Module attributes
5. Conditional config blocks (`if config_env() == :prod`)

## Output

### Dry Run Mode
```
Would convert config/runtime.exs:
  Line 5: System.fetch_env!("PORT") |> String.to_integer()
       -> fetch_env_as_integer!("PORT")
  
  Line 8: System.get_env("ENABLED") in ~w[1 true]
       -> get_env_as_boolean("ENABLED")

Would add: import Enviable (after line 1)

Total: 2 conversions
```

### Normal Mode
```
Converting config/runtime.exs...
  ✓ Added import Enviable
  ✓ Converted 2 System.get_env calls
  ✓ Converted 1 System.fetch_env! call

Backup saved to: config/runtime.exs.backup
```

## Implementation Notes

### AST Approach
- Parse file with `Code.string_to_quoted!/2` with `token_metadata: true, columns: true`
- Walk AST to find conversion patterns
- Build replacement AST nodes
- Use `Macro.to_string/2` to generate code
- Preserve original formatting where possible using token metadata

### Pattern Matching Strategy
1. Match on `System.get_env` / `System.fetch_env!` calls
2. Look for pipe operators or function wrapping
3. Identify conversion function (String.to_integer, etc.)
4. Extract variable name and default value
5. Generate appropriate Enviable function call

### Limitations
- May not preserve exact formatting in all cases
- Complex nested expressions might not convert
- Custom conversion functions won't be recognized
- Requires manual review of changes

## Testing Strategy

### Test Cases
1. Empty file
2. File with no System calls
3. File with only simple System calls
4. File with all conversion patterns
5. File with complex expressions
6. File with existing Enviable imports
7. File with comments and formatting
8. File with conditional config blocks

### Validation
- Run `mix format` after conversion
- Ensure file compiles
- Compare behavior with original (if possible)
