from __future__ import annotations

import argparse
import os
import shlex
import shutil
import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Sequence
import json

from rich.console import Console
from rich.panel import Panel
from rich.prompt import Confirm, Prompt
from rich.table import Table

console = Console()


@dataclass(frozen=True)
class PaneSpec:
    title: str
    command_template: tuple[str, ...] = ()


@dataclass(frozen=True)
class LayoutPreset:
    name: str
    pane_count: int
    description: str
    pane_specs: tuple[PaneSpec, ...] = ()

    @property
    def pane_titles(self) -> tuple[str, ...]:
        if self.pane_specs:
            return tuple(spec.title for spec in self.pane_specs)
        return tuple(f"pane-{index + 1}" for index in range(self.pane_count))


def _powershell_script(message: str, cwd: str) -> tuple[str, ...]:
    return (
        "powershell.exe",
        "-NoLogo",
        "-NoExit",
        "-Command",
        f"Write-Host '{message}'; Set-Location '{cwd}'",
    )


def _powershell_command(script: str, cwd: str) -> tuple[str, ...]:
    return (
        "powershell.exe",
        "-NoLogo",
        "-NoExit",
        "-Command",
        f"Set-Location '{cwd}'; {script}",
    )


PRESETS: dict[str, LayoutPreset] = {
    "dev": LayoutPreset(
        name="dev",
        pane_count=3,
        description="Editor, tests, and logs.",
        pane_specs=(
            PaneSpec("dev", _powershell_script("Dev workspace", "{cwd}")),
            PaneSpec("tests", _powershell_script("Run tests here", "{cwd}")),
            PaneSpec("logs", _powershell_script("Watch logs here", "{cwd}")),
        ),
    ),
    "trade": LayoutPreset(
        name="trade",
        pane_count=4,
        description="Market, orders, logs, and notes.",
        pane_specs=(
            PaneSpec("market", _powershell_script("Market feed", "{cwd}")),
            PaneSpec("orders", _powershell_script("Order control", "{cwd}")),
            PaneSpec("logs", _powershell_script("Audit logs", "{cwd}")),
            PaneSpec("notes", _powershell_script("Trading notes", "{cwd}")),
        ),
    ),
    "monitor": LayoutPreset(
        name="monitor",
        pane_count=2,
        description="Live monitoring and quick checks.",
        pane_specs=(
            PaneSpec("monitor", _powershell_script("Live monitor", "{cwd}")),
            PaneSpec("checks", _powershell_script("Quick checks", "{cwd}")),
        ),
    ),
    "web": LayoutPreset(
        name="web",
        pane_count=3,
        description="Frontend, backend, and logs.",
        pane_specs=(
            PaneSpec(
                "frontend",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; npm run dev",
                ),
            ),
            PaneSpec(
                "backend",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; python -m uvicorn app.main:app --reload",
                ),
            ),
            PaneSpec(
                "logs",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; Get-ChildItem .\\logs -Force",
                ),
            ),
        ),
    ),
    "api": LayoutPreset(
        name="api",
        pane_count=3,
        description="API server, tests, and docs.",
        pane_specs=(
            PaneSpec("api", _powershell_script("API workspace", "{cwd}")),
            PaneSpec("tests", _powershell_script("API tests", "{cwd}")),
            PaneSpec("docs", _powershell_script("API docs", "{cwd}")),
        ),
    ),
    "logs": LayoutPreset(
        name="logs",
        pane_count=2,
        description="Log watcher and log index.",
        pane_specs=(
            PaneSpec(
                "tail",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "if (Test-Path '{cwd}\\logs') { Get-ChildItem '{cwd}\\logs' -Force } else { Write-Host 'No logs directory yet' }",
                ),
            ),
            PaneSpec(
                "index",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; Get-ChildItem -Force | Select-Object Name,Length,LastWriteTime",
                ),
            ),
        ),
    ),
    "db": LayoutPreset(
        name="db",
        pane_count=3,
        description="Database files, migrations, and exports.",
        pane_specs=(
            PaneSpec("db", _powershell_script("Database workspace", "{cwd}")),
            PaneSpec("migrations", _powershell_script("Migrations", "{cwd}")),
            PaneSpec("exports", _powershell_script("Exports", "{cwd}")),
        ),
    ),
    "pipeline": LayoutPreset(
        name="pipeline",
        pane_count=3,
        description="Data pipeline, tests, and outputs.",
        pane_specs=(
            PaneSpec(
                "pipeline",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; python -m examples.pipeline_final_template",
                ),
            ),
            PaneSpec(
                "tests",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; pytest tests -q",
                ),
            ),
            PaneSpec(
                "outputs",
                (
                    "powershell.exe",
                    "-NoLogo",
                    "-NoExit",
                    "-Command",
                    "Set-Location '{cwd}'; Get-ChildItem .\\data -Force",
                ),
            ),
        ),
    ),
}

COMPLETE_VALUES = [
    "1",
    "2",
    "3",
    "4",
    *sorted(PRESETS),
    "suggest",
    "completion",
    "install-completion",
    "wizard",
    "dry-run",
    "menu",
    "recent",
]
MAX_AUTO_LAYOUT_PANES = 12
INSTALL_MARKER_START = "# >>> soigia completion >>>"
INSTALL_MARKER_END = "# <<< soigia completion <<<"
RECENT_ENV_PATH = "SOIGIA_RECENT_PATH"
RECENT_LIMIT = 3


class CliError(RuntimeError):
    """Raised when the CLI cannot continue with a user-facing error."""


def _positive_int(value: str) -> int:
    try:
        pane_count = int(value)
    except ValueError as exc:
        raise argparse.ArgumentTypeError(f"invalid pane count: {value!r}") from exc

    if pane_count < 1:
        raise argparse.ArgumentTypeError("pane count must be a positive integer")
    return pane_count


def _is_windows() -> bool:
    return sys.platform == "win32"


def _find_wt_executable() -> str:
    candidates = [
        shutil.which("wt"),
        shutil.which("wt.exe"),
    ]

    local_app_data = os.environ.get("LOCALAPPDATA")
    if local_app_data:
        candidates.append(str(Path(local_app_data) / "Microsoft" / "WindowsApps" / "wt.exe"))

    for candidate in candidates:
        if candidate and Path(candidate).exists():
            return candidate

    raise CliError(
        "Windows Terminal was not found. Install it from the Microsoft Store or run "
        "`winget install Microsoft.WindowsTerminal`."
    )


def _detect_shell() -> str:
    shell = os.environ.get("SHELL", "").lower()
    if "zsh" in shell:
        return "zsh"
    if "bash" in shell:
        return "bash"

    if sys.platform == "win32":
        if os.environ.get("PSModulePath") or os.environ.get("TERM_PROGRAM", "").lower() == "vscode":
            return "powershell"
        if os.environ.get("COMSPEC", "").lower().endswith("cmd.exe"):
            return "powershell"

    return "powershell"


def _recent_path() -> Path:
    override = os.environ.get(RECENT_ENV_PATH)
    if override:
        return Path(override).expanduser().resolve()
    return Path.home() / ".soigia_recent.json"


def _load_recent_layouts() -> list[str]:
    path = _recent_path()
    if not path.exists():
        return []
    try:
        data = json.loads(path.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return []
    if not isinstance(data, list):
        return []
    return [str(item) for item in data if isinstance(item, str)]


def _save_recent_layouts(layouts: Sequence[str]) -> None:
    path = _recent_path()
    try:
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(json.dumps(list(layouts[:RECENT_LIMIT]), indent=2), encoding="utf-8")
    except OSError:
        # Recent layouts are a convenience feature only.
        return


def _remember_layout(layout: str) -> None:
    layouts = [layout]
    for item in _load_recent_layouts():
        if item != layout:
            layouts.append(item)
    _save_recent_layouts(layouts[:RECENT_LIMIT])


def _resolve_layout_target(value: str) -> tuple[int, LayoutPreset | None]:
    lowered = value.lower()
    if value.isdigit():
        return _positive_int(value), None

    preset = PRESETS.get(lowered)
    if preset is not None:
        return preset.pane_count, preset

    raise argparse.ArgumentTypeError(
        f"unknown layout {value!r}. Use a positive integer, one of: {', '.join(sorted(PRESETS))}, "
        "or `suggest`/`completion`."
    )


def _parse_pane_command(value: str) -> tuple[str, ...]:
    parts = shlex.split(value, posix=False)
    if not parts:
        raise argparse.ArgumentTypeError("pane command must not be empty")
    return tuple(parts)


def _normalize_command_template(template: Sequence[str], cwd: str) -> tuple[str, ...]:
    return tuple(part.replace("{cwd}", cwd) for part in template)


def _append_segment(command: list[str], segment: list[str]) -> None:
    if len(command) > 1:
        command.append(";")
    command.extend(segment)


def _pane_segment(title: str, cwd: str, command_template: Sequence[str] | None = None) -> list[str]:
    segment = ["--title", title]
    if command_template:
        segment.extend(_normalize_command_template(command_template, cwd))
    return segment


def _build_default_pane_specs(pane_count: int) -> tuple[PaneSpec, ...]:
    return tuple(PaneSpec(title=f"pane-{index + 1}") for index in range(pane_count))


def _build_pane_specs(
    pane_count: int,
    *,
    preset: LayoutPreset | None = None,
    pane_titles: Sequence[str] | None = None,
    pane_commands: Sequence[Sequence[str]] | None = None,
) -> tuple[PaneSpec, ...]:
    base_specs = list(preset.pane_specs if preset and preset.pane_specs else _build_default_pane_specs(pane_count))

    if len(base_specs) < pane_count:
        base_specs.extend(PaneSpec(title=f"pane-{index + 1}") for index in range(len(base_specs), pane_count))

    if pane_titles:
        for index, title in enumerate(pane_titles[:pane_count]):
            base_specs[index] = PaneSpec(title=title, command_template=base_specs[index].command_template)

    if pane_commands:
        for index, command in enumerate(pane_commands[:pane_count]):
            base_specs[index] = PaneSpec(title=base_specs[index].title, command_template=tuple(command))

    return tuple(base_specs[:pane_count])


def _build_wt_command(pane_specs: Sequence[PaneSpec], cwd: str) -> list[str]:
    wt = _find_wt_executable()
    command: list[str] = [wt]

    if not pane_specs:
        raise CliError("layout must contain at least one pane")

    first = pane_specs[0]
    command.extend(["new-tab", "-d", cwd, *_pane_segment(first.title, cwd, first.command_template)])

    if len(pane_specs) >= 2:
        second = pane_specs[1]
        _append_segment(command, ["split-pane", "-V", "-d", cwd, *_pane_segment(second.title, cwd, second.command_template)])

    if len(pane_specs) >= 3:
        third = pane_specs[2]
        _append_segment(command, ["move-focus", "left"])
        _append_segment(command, ["split-pane", "-H", "-d", cwd, *_pane_segment(third.title, cwd, third.command_template)])

    if len(pane_specs) >= 4:
        fourth = pane_specs[3]
        _append_segment(command, ["move-focus", "right"])
        _append_segment(command, ["split-pane", "-H", "-d", cwd, *_pane_segment(fourth.title, cwd, fourth.command_template)])

    for index in range(4, len(pane_specs)):
        spec = pane_specs[index]
        direction = "-V" if index % 2 == 0 else "-H"
        _append_segment(command, ["split-pane", direction, "-d", cwd, *_pane_segment(spec.title, cwd, spec.command_template)])

    return command


def _render_command(command: Sequence[str]) -> str:
    rendered: list[str] = []
    for token in command:
        if token == ";":
            rendered.append(";")
        else:
            rendered.append(subprocess.list2cmdline([token]))
    return " ".join(rendered)


def _run_command(command: Sequence[str]) -> None:
    subprocess.run(list(command), check=True)


def _build_parser() -> argparse.ArgumentParser:
    epilog = (
        "Examples:\n"
        "  soigia 4\n"
        "  soigia dev\n"
        "  soigia api --pane-cmd \"powershell.exe -NoExit -Command python -m uvicorn app.main:app --reload\"\n"
        "  soigia suggest\n"
        "  soigia completion powershell\n"
        "  soigia --install-completion\n"
        "  soigia wizard\n"
        "  soigia 4 --dry-run\n"
    )
    parser = argparse.ArgumentParser(
        prog="soigia",
        description="Open Windows Terminal and split it into a chosen number of panes.",
        epilog=epilog,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.add_argument(
        "target",
        nargs="?",
        default="4",
        help="Pane count, preset name, or `wizard`. Try `soigia suggest` for ideas.",
    )
    parser.add_argument(
        "extra",
        nargs="?",
        default=None,
        help=argparse.SUPPRESS,
    )
    parser.add_argument(
        "--cwd",
        default=None,
        help="Working directory to open in every pane. Defaults to the current directory.",
    )
    parser.add_argument(
        "--pane-title",
        action="append",
        default=None,
        help="Override pane titles in order. Repeat once per pane you want to rename.",
    )
    parser.add_argument(
        "--pane-cmd",
        action="append",
        default=None,
        help="Override pane commands in order. Repeat once per pane you want to customize.",
    )
    parser.add_argument(
        "--install-completion",
        action="store_true",
        help="Install shell completion into your profile or rc file.",
    )
    parser.add_argument(
        "--completion-shell",
        choices=("powershell", "bash", "zsh"),
        default=None,
        help="Shell to use for `completion` and `--install-completion`. Auto-detected if omitted.",
    )
    parser.add_argument(
        "--profile",
        default=None,
        help="Override the profile/rc file used by --install-completion.",
    )
    parser.add_argument(
        "--list-presets",
        action="store_true",
        help="List available presets and exit.",
    )
    parser.add_argument(
        "--show-layout",
        action="store_true",
        help="Preview the selected layout and exit.",
    )
    parser.add_argument(
        "--dry-run",
        action="store_true",
        help="Print the command that would run and exit without opening Windows Terminal.",
    )
    parser.add_argument(
        "--wizard",
        action="store_true",
        help="Use an interactive layout wizard.",
    )
    return parser


def _render_suggestions() -> None:
    table = Table(title="Soi Gia CLI Suggestions", show_header=True, header_style="bold cyan")
    table.add_column("Target", style="bold")
    table.add_column("Panes", justify="right")
    table.add_column("Use case")
    table.add_row("1", "1", "Single terminal")
    table.add_row("2", "2", "Quick split for side-by-side work")
    table.add_row("3", "3", "Editor + tests + logs")
    table.add_row("4", "4", "Balanced 2x2 workspace")

    for preset in PRESETS.values():
        table.add_row(preset.name, str(preset.pane_count), preset.description)

    console.print(table)
    console.print()
    console.print(
        Panel.fit(
            "[bold]Autocomplete[/bold]\n"
            "Run [cyan]soigia --install-completion[/cyan] or [cyan]soigia completion powershell[/cyan].\n"
            "Tab completion will then suggest pane counts and presets.",
            border_style="bright_blue",
            title="Modern CLI",
        )
    )


def _menu_box(title: str, lines: Sequence[str]) -> None:
    body = "\n".join(lines)
    console.print(Panel.fit(body, border_style="bright_blue", title=title))


def _render_menu_group(title: str, border_style: str, items: Sequence[tuple[str, str]]) -> None:
    table = Table(title=title, show_header=True, header_style=f"bold {border_style}")
    table.add_column("#", justify="right")
    table.add_column("Key", style="bold")
    table.add_column("Description")
    for index, (key, description) in enumerate(items, start=1):
        table.add_row(str(index), key, description)
    console.print(Panel.fit(table, border_style=border_style, title=title))


def _menu_prompt(title: str, options: Sequence[tuple[str, str]]) -> str:
    table = Table(title=title, show_header=True, header_style="bold cyan")
    table.add_column("#", justify="right")
    table.add_column("Key", style="bold")
    table.add_column("Description")
    for index, (key, description) in enumerate(options, start=1):
        table.add_row(str(index), key, description)
    console.print(table)
    choices = {str(index): key for index, (key, _) in enumerate(options, start=1)}
    while True:
        answer = Prompt.ask("Choose", choices=list(choices.keys()), default="1")
        return choices[answer]


def _menu_preview_header() -> None:
    _menu_box(
        "Soi Gia Menu",
        [
            "A practical Windows Terminal launcher.",
            "Use numbered choices for speed.",
            "Type a number, press Enter, and keep moving.",
        ],
    )


def _render_main_menu() -> None:
    console.print(Panel.fit("Open a template, inspect it, or build one from scratch.", border_style="bright_blue", title="Main Menu"))
    _render_menu_group(
        "Recent",
        "yellow",
        [
            ("recent", "Open a recently used layout"),
        ],
    )
    _render_menu_group(
        "Quick Launch",
        "cyan",
        [
            ("dev", "Developer workspace"),
            ("web", "Frontend + backend + logs"),
            ("api", "API server + tests + docs"),
            ("pipeline", "Pipeline + tests + outputs"),
            ("db", "Database + migrations + exports"),
            ("logs", "Log watcher + log index"),
            ("trade", "Market + orders + logs + notes"),
            ("monitor", "Live monitoring + quick checks"),
        ],
    )
    _render_menu_group(
        "Utilities",
        "magenta",
        [
            ("custom", "Build a custom layout"),
            ("suggest", "Show all suggestions"),
            ("completion", "Show completion script"),
            ("install-completion", "Install completion script"),
            ("dry-run", "Preview command only"),
        ],
    )
    _render_menu_group(
        "Diagnostics",
        "green",
        [
            ("show-layout", "Preview current layout"),
            ("list-presets", "List all presets"),
            ("wizard", "Interactive wizard"),
            ("exit", "Leave the menu"),
        ],
    )


def _completion_script(shell: str) -> str:
    shell = shell.lower()
    values = " ".join(COMPLETE_VALUES)

    if shell == "powershell":
        quoted_values = ", ".join(f"'{value}'" for value in COMPLETE_VALUES)
        return f"""
Register-ArgumentCompleter -Native -CommandName soigia -ScriptBlock {{
    param($wordToComplete, $commandAst, $cursorPosition)
    $values = @({quoted_values})
    $values |
        Where-Object {{ $_ -like "$wordToComplete*" }} |
        ForEach-Object {{
            [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
        }}
}}
""".strip()

    if shell == "bash":
        return f"""
_soigia_complete() {{
    local cur
    cur="${{COMP_WORDS[COMP_CWORD]}}"
    COMPREPLY=( $(compgen -W "{values}" -- "$cur") )
}}
complete -F _soigia_complete soigia
""".strip()

    if shell == "zsh":
        return f"""
#compdef soigia
_arguments "1: :(({values}))"
""".strip()

    raise CliError(f"unsupported completion shell: {shell!r}")


def _render_presets_table() -> None:
    table = Table(title="Available presets", show_header=True, header_style="bold cyan")
    table.add_column("Name", style="bold")
    table.add_column("Panes", justify="right")
    table.add_column("Description")
    table.add_column("Titles")
    table.add_column("Commands")
    table.add_row("1", "1", "Single terminal", "soigia", "default shell")
    table.add_row("2", "2", "Side-by-side work", "pane-1, pane-2", "default shell")
    table.add_row("3", "3", "Editor / tests / logs", "pane-1, pane-2, pane-3", "default shell")
    table.add_row("4", "4", "Balanced 2x2 workspace", "pane-1, pane-2, pane-3, pane-4", "default shell")
    for preset in PRESETS.values():
        command_hint = "custom panes" if preset.pane_specs else "default shell"
        table.add_row(preset.name, str(preset.pane_count), preset.description, ", ".join(preset.pane_titles), command_hint)
    console.print(table)


def _preview_layout(target: str, pane_specs: Sequence[PaneSpec]) -> None:
    table = Table(title=f"Layout preview: {target}", show_header=True, header_style="bold magenta")
    table.add_column("Pane", justify="right")
    table.add_column("Title")
    table.add_column("Command")
    roles = ["primary", "secondary", "tertiary", "quaternary"]
    for index, spec in enumerate(pane_specs):
        role = roles[index] if index < len(roles) else "extra"
        command_text = " ".join(spec.command_template) if spec.command_template else "default shell"
        table.add_row(str(index + 1), f"{spec.title} [{role}]", command_text)
    console.print(table)


def _show_command_preview(command: Sequence[str]) -> None:
    console.print(Panel.fit(_render_command(command), border_style="yellow", title="Command Preview"))


def _launch_layout(
    parser: argparse.ArgumentParser,
    target: str,
    cwd: str,
    *,
    preset: LayoutPreset | None = None,
    pane_count: int | None = None,
    pane_titles: Sequence[str] | None = None,
    pane_commands: Sequence[Sequence[str]] | None = None,
    dry_run: bool = False,
    show_layout: bool = False,
) -> int:
    resolved_pane_count = pane_count if pane_count is not None else _resolve_layout_target(target)[0]
    if resolved_pane_count > MAX_AUTO_LAYOUT_PANES:
        parser.error(
            f"pane count {resolved_pane_count} is too large for the simple layout builder; max is {MAX_AUTO_LAYOUT_PANES}"
        )

    pane_specs = _build_pane_specs(
        resolved_pane_count,
        preset=preset,
        pane_titles=pane_titles,
        pane_commands=pane_commands,
    )

    if show_layout:
        _preview_layout(target, pane_specs)
        return 0

    command = _build_wt_command(pane_specs, cwd)
    if dry_run:
        _show_command_preview(command)
        return 0

    _run_command(command)
    _remember_layout(target)
    return 0


def _profile_path(shell: str, explicit: str | None = None) -> Path:
    if explicit:
        return Path(explicit).expanduser().resolve()

    home = Path.home()
    shell = shell.lower()

    if shell == "powershell":
        windows_powershell = home / "Documents" / "WindowsPowerShell" / "Microsoft.PowerShell_profile.ps1"
        powershell_7 = home / "Documents" / "PowerShell" / "Microsoft.PowerShell_profile.ps1"
        return powershell_7 if powershell_7.exists() or not windows_powershell.exists() else windows_powershell

    if shell == "bash":
        return home / ".bashrc"

    if shell == "zsh":
        return home / ".zshrc"

    raise CliError(f"unsupported shell: {shell!r}")


def _install_completion(shell: str, profile: str | None = None) -> Path:
    script = _completion_script(shell)
    target = _profile_path(shell, profile)
    target.parent.mkdir(parents=True, exist_ok=True)

    block = f"{INSTALL_MARKER_START}\n{script}\n{INSTALL_MARKER_END}\n"
    existing = target.read_text(encoding="utf-8") if target.exists() else ""

    if INSTALL_MARKER_START in existing and INSTALL_MARKER_END in existing:
        prefix, remainder = existing.split(INSTALL_MARKER_START, 1)
        _, suffix = remainder.split(INSTALL_MARKER_END, 1)
        updated = prefix + block + suffix.lstrip("\n")
    else:
        updated = existing.rstrip("\n") + ("\n\n" if existing.strip() else "") + block

    target.write_text(updated, encoding="utf-8")
    return target


def _print_completion(shell: str) -> None:
    print(_completion_script(shell))


def _normalize_cwd(cwd: str | None) -> str:
    path = Path(cwd or os.getcwd()).expanduser().resolve()
    return str(path)


def _print_install_result(path: Path, shell: str) -> None:
    console.print(
        Panel.fit(
            f"[bold green]Installed[/bold green]\n"
            f"Shell: [cyan]{shell}[/cyan]\n"
            f"Profile: [cyan]{path}[/cyan]\n\n"
            "Open a new terminal session for the completion to take effect.",
            border_style="green",
            title="Completion Ready",
        )
    )


def _prompt_wizard_target() -> str:
    console.print(Panel.fit("Pick a layout to open.", border_style="bright_blue", title="Soi Gia Wizard"))
    _render_presets_table()
    choices = ["1", "2", "3", "4", *sorted(PRESETS)]
    return Prompt.ask("Layout", choices=choices + ["custom"], default="dev")


def _prompt_wizard_spec(pane_count: int, preset: LayoutPreset | None) -> tuple[Sequence[str], Sequence[Sequence[str]]]:
    titles: list[str] = []
    commands: list[tuple[str, ...]] = []
    defaults = preset.pane_specs if preset and preset.pane_specs else _build_default_pane_specs(pane_count)

    for index in range(pane_count):
        default_title = defaults[index].title if index < len(defaults) else f"pane-{index + 1}"
        title = Prompt.ask(f"Title for pane {index + 1}", default=default_title)
        titles.append(title)

        if Confirm.ask(f"Add custom command for pane {index + 1}?", default=False):
            command_text = Prompt.ask("Command", default="")
            commands.append(_parse_pane_command(command_text))
        elif index < len(defaults) and defaults[index].command_template:
            commands.append(defaults[index].command_template)

    return titles, commands


def _run_wizard(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
    layout_target = _prompt_wizard_target()
    if layout_target == "custom":
        pane_count = _positive_int(Prompt.ask("Pane count", default="4"))
        preset = None
    else:
        pane_count, preset = _resolve_layout_target(layout_target)

    cwd = _normalize_cwd(Prompt.ask("Working directory", default=args.cwd or os.getcwd()))
    titles, commands = _prompt_wizard_spec(pane_count, preset)

    pane_specs = _build_pane_specs(
        pane_count,
        preset=preset,
        pane_titles=titles,
        pane_commands=commands,
    )

    if Confirm.ask("Preview layout before opening?", default=True):
        _preview_layout(layout_target, pane_specs)

    command = _build_wt_command(pane_specs, cwd)
    _run_command(command)
    _remember_layout(layout_target)
    return 0


def _preset_submenu(parser: argparse.ArgumentParser, args: argparse.Namespace, preset: LayoutPreset) -> int:
    _menu_box(
        f"Preset: {preset.name}",
        [
            f"Description: {preset.description}",
            f"Panes: {preset.pane_count}",
            "Pick an action below.",
        ],
    )
    action = _menu_prompt(
        f"{preset.name} actions",
        [
            ("launch", "Open the preset now"),
            ("preview", "Preview layout only"),
            ("dry-run", "Print wt command only"),
            ("customize", "Edit titles or commands first"),
            ("back", "Return to main menu"),
        ],
    )

    cwd = _normalize_cwd(args.cwd)

    if action == "back":
        return _run_menu(parser, args)

    if action == "preview":
        pane_specs = _build_pane_specs(preset.pane_count, preset=preset)
        _preview_layout(preset.name, pane_specs)
        return 0

    if action == "dry-run":
        pane_specs = _build_pane_specs(preset.pane_count, preset=preset)
        _show_command_preview(_build_wt_command(pane_specs, cwd))
        return 0

    if action == "customize":
        pane_titles, pane_commands = _prompt_wizard_spec(preset.pane_count, preset)
        pane_specs = _build_pane_specs(
            preset.pane_count,
            preset=preset,
            pane_titles=pane_titles,
            pane_commands=pane_commands,
        )
        if Confirm.ask("Preview customized layout before opening?", default=True):
            _preview_layout(preset.name, pane_specs)
        if Confirm.ask("Dry run instead of opening?", default=False):
            _show_command_preview(_build_wt_command(pane_specs, cwd))
            return 0
        _run_command(_build_wt_command(pane_specs, cwd))
        _remember_layout(preset.name)
        return 0

    if action == "launch":
        pane_specs = _build_pane_specs(preset.pane_count, preset=preset)
        _run_command(_build_wt_command(pane_specs, cwd))
        _remember_layout(preset.name)
        return 0

    parser.error(f"unknown preset action: {action!r}")
    return 1


def _run_menu(parser: argparse.ArgumentParser, args: argparse.Namespace) -> int:
    _menu_preview_header()
    _render_main_menu()
    choice = _menu_prompt(
        "Main Menu",
        [
            ("recent", "Open a recently used layout"),
            ("dev", "Developer workspace"),
            ("web", "Frontend + backend + logs"),
            ("api", "API server + tests + docs"),
            ("pipeline", "Pipeline + tests + outputs"),
            ("db", "Database + migrations + exports"),
            ("logs", "Log watcher + log index"),
            ("trade", "Market + orders + logs + notes"),
            ("monitor", "Live monitoring + quick checks"),
            ("custom", "Build a custom layout"),
            ("suggest", "Show all suggestions"),
            ("completion", "Show completion script"),
            ("install-completion", "Install completion script"),
        ],
    )

    if choice == "recent":
        recents = _load_recent_layouts()
        if not recents:
            console.print(Panel.fit("No recent layouts yet.", border_style="yellow", title="Recent"))
            return 0
        recent_choice = _menu_prompt(
            "Recent layouts",
            [(item, f"Open {item} again") for item in recents],
        )
        if recent_choice in PRESETS:
            return _preset_submenu(parser, args, PRESETS[recent_choice])
        cwd = _normalize_cwd(args.cwd)
        pane_count, preset = _resolve_layout_target(recent_choice)
        return _launch_layout(
            parser,
            recent_choice,
            cwd,
            preset=preset,
            pane_count=pane_count,
            dry_run=False,
            show_layout=False,
        )

    if choice in PRESETS:
        return _preset_submenu(parser, args, PRESETS[choice])

    if choice == "suggest":
        _render_suggestions()
        return 0

    if choice == "completion":
        shell = args.completion_shell or _detect_shell()
        _print_completion(shell)
        return 0

    if choice == "install-completion":
        shell = args.completion_shell or _detect_shell()
        profile_path = _install_completion(shell, args.profile)
        _print_install_result(profile_path, shell)
        return 0

    if choice == "custom":
        pane_count = _positive_int(Prompt.ask("Pane count", default="4"))
        pane_titles = [Prompt.ask(f"Title for pane {index + 1}", default=f"pane-{index + 1}") for index in range(pane_count)]
        pane_commands: list[tuple[str, ...]] = []
        for index in range(pane_count):
            if Confirm.ask(f"Add custom command for pane {index + 1}?", default=False):
                pane_commands.append(_parse_pane_command(Prompt.ask("Command", default="powershell.exe -NoExit")))
        cwd = _normalize_cwd(Prompt.ask("Working directory", default=args.cwd or os.getcwd()))
        return _launch_layout(
            parser,
            "custom",
            cwd,
            pane_count=pane_count,
            pane_titles=pane_titles,
            pane_commands=pane_commands,
            dry_run=False,
            show_layout=False,
        )

    if choice == "dry-run":
        target = Prompt.ask("Layout or preset", choices=["1", "2", "3", "4", *sorted(PRESETS)], default="dev")
        cwd = _normalize_cwd(args.cwd)
        pane_count, preset = _resolve_layout_target(target)
        pane_specs = _build_pane_specs(
            pane_count,
            preset=preset,
            pane_titles=args.pane_title or None,
            pane_commands=[_parse_pane_command(cmd) for cmd in args.pane_cmd] if args.pane_cmd else None,
        )
        _show_command_preview(_build_wt_command(pane_specs, cwd))
        return 0

    parser.error(f"unknown menu choice: {choice!r}")
    return 1


def main(argv: list[str] | None = None) -> int:
    parser = _build_parser()
    args = parser.parse_args(argv)

    if args.list_presets:
        _render_presets_table()
        return 0

    if args.install_completion:
        try:
            shell = args.completion_shell or _detect_shell()
            profile_path = _install_completion(shell, args.profile)
            _print_install_result(profile_path, shell)
        except CliError as exc:
            parser.error(str(exc))
        return 0

    if not _is_windows():
        parser.error("this CLI only works on Windows because it depends on Windows Terminal")

    target = args.target.lower()

    if args.wizard or target == "wizard":
        try:
            return _run_wizard(parser, args)
        except argparse.ArgumentTypeError as exc:
            parser.error(str(exc))
        except CliError as exc:
            parser.error(str(exc))
        except FileNotFoundError:
            parser.error("could not run `wt`; Windows Terminal is not installed or not on PATH")
        except subprocess.CalledProcessError as exc:
            parser.error(f"Windows Terminal exited with status {exc.returncode}")

    if target == "menu":
        try:
            return _run_menu(parser, args)
        except argparse.ArgumentTypeError as exc:
            parser.error(str(exc))
        except CliError as exc:
            parser.error(str(exc))
        except FileNotFoundError:
            parser.error("could not run `wt`; Windows Terminal is not installed or not on PATH")
        except subprocess.CalledProcessError as exc:
            parser.error(f"Windows Terminal exited with status {exc.returncode}")

    if target == "suggest":
        _render_suggestions()
        return 0

    if target == "completion":
        shell = (args.extra or args.completion_shell or _detect_shell()).lower()
        try:
            _print_completion(shell)
        except CliError as exc:
            parser.error(str(exc))
        return 0

    try:
        cwd = _normalize_cwd(args.cwd)
        pane_count, preset = _resolve_layout_target(args.target)
        return _launch_layout(
            parser,
            args.target,
            cwd,
            preset=preset,
            pane_count=pane_count,
            pane_titles=args.pane_title or None,
            pane_commands=[_parse_pane_command(cmd) for cmd in args.pane_cmd] if args.pane_cmd else None,
            dry_run=args.dry_run,
            show_layout=args.show_layout,
        )
    except argparse.ArgumentTypeError as exc:
        parser.error(str(exc))
    except CliError as exc:
        parser.error(str(exc))
    except FileNotFoundError:
        parser.error("could not run `wt`; Windows Terminal is not installed or not on PATH")
    except subprocess.CalledProcessError as exc:
        parser.error(f"Windows Terminal exited with status {exc.returncode}")

    return 0


if __name__ == "__main__":
    main()
