import Foundation

enum StartupScriptInstaller {
    /// What to launch for a session, including any env overrides needed to make
    /// the startup script source after the user's normal init.
    struct Plan: Equatable {
        let executable: String
        let args: [String]
        /// Env vars that the surface launcher MUST set on top of whatever else
        /// it's already passing to the shell.
        let envOverrides: [String: String]
    }

    /// Returns the launch plan for a session.
    ///
    /// When `startupScript` is empty: launch the configured shell as a login
    /// shell (`-l`) with no extra setup.
    ///
    /// Otherwise: write the script to a per-session rc file and ask the shell
    /// to source it after the user's normal init. Mechanism is shell-specific:
    ///
    /// - **zsh**: write `<dir>/.zshrc` (which sources `~/.zshrc` first then the
    ///   user's snippet) and set `ZDOTDIR=<dir>` so zsh picks it up. zsh does
    ///   NOT accept bash's `--rcfile`, so the previous bash-style flag would
    ///   silently fail.
    /// - **bash**: pass `--rcfile <file> -i` (still sources `~/.bashrc` first
    ///   inside the file).
    /// - **other shells**: log a warning and ignore the startup script. We don't
    ///   know how to inject one in fish/nu/etc. without per-shell heuristics.
    static func plan(
        shell: String,
        startupScript: String,
        sessionId: String
    ) throws -> Plan {
        let trimmed = startupScript.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty else {
            return Plan(executable: shell, args: ["-l"], envOverrides: [:])
        }

        let basename = (shell as NSString).lastPathComponent

        switch basename {
        case "zsh":
            // ZDOTDIR-scoped .zshrc that explicitly sources the user's full
            // login + interactive init chain (.zshenv/.zprofile/.zshrc/.zlogin
            // from $HOME) then the user-supplied snippet. We can't rely on
            // zsh's own login-shell auto-loading because once we override
            // ZDOTDIR, zsh looks for .zprofile/.zlogin in OUR dir, not the
            // user's home. Pass `-l` to keep login-shell semantics for $0,
            // hashed binaries, etc.; zsh's own attempts to source .zprofile /
            // .zlogin from our ZDOTDIR are silent no-ops because those files
            // don't exist there.
            let dir = Paths.appSupportRoot
                .appendingPathComponent("rcfiles", isDirectory: true)
                .appendingPathComponent(sessionId, isDirectory: true)
            try Paths.ensureDirectoryExists(dir)
            let rcfile = dir.appendingPathComponent(".zshrc")
            let body = """
            # Generated by Alas — sources the user's standard zsh init chain
            # then appends the per-session startup script. zsh's normal login
            # search path is bypassed when ZDOTDIR is overridden, so we re-source
            # the canonical user init files explicitly.
            [ -f "$HOME/.zshenv" ]   && source "$HOME/.zshenv"
            [ -f "$HOME/.zprofile" ] && source "$HOME/.zprofile"
            [ -f "$HOME/.zshrc" ]    && source "$HOME/.zshrc"
            [ -f "$HOME/.zlogin" ]   && source "$HOME/.zlogin"
            \(trimmed)
            """
            try body.write(to: rcfile, atomically: true, encoding: .utf8)
            return Plan(
                executable: shell,
                args: ["-l", "-i"],
                envOverrides: ["ZDOTDIR": dir.path]
            )

        case "bash":
            // bash quirks: `--rcfile <file>` is only honored in non-login
            // interactive mode. Adding `-l` makes it a login shell and bash
            // ignores --rcfile entirely (sourcing /etc/profile + ~/.bash_profile
            // / ~/.bash_login / ~/.profile instead) — which would skip the
            // user's custom startup snippet. So we DON'T pass `-l`. Instead our
            // generated rcfile manually sources the login init chain so the
            // user's PATH/toolchains/etc. still load. Net behavior: same files
            // are sourced as a login shell + the user snippet runs.
            let dir = Paths.appSupportRoot
                .appendingPathComponent("rcfiles", isDirectory: true)
            try Paths.ensureDirectoryExists(dir)
            let rcfile = dir.appendingPathComponent("\(sessionId).bashrc")
            let body = """
            # Generated by Alas — sources the user's standard bash init chain
            # then appends the per-session startup script. bash's normal login
            # search path is skipped when --rcfile is used, so we re-source
            # the canonical user init files explicitly. Match real bash login
            # semantics: source /etc/profile, then EXACTLY ONE of
            # ~/.bash_profile / ~/.bash_login / ~/.profile (first readable
            # wins), then ~/.bashrc for interactive setup. Sourcing all
            # three login candidates would double-init common setups
            # (PATH mutations, NVM, etc.).
            [ -f "/etc/profile" ] && source "/etc/profile"
            if   [ -f "$HOME/.bash_profile" ]; then source "$HOME/.bash_profile"
            elif [ -f "$HOME/.bash_login" ];   then source "$HOME/.bash_login"
            elif [ -f "$HOME/.profile" ];      then source "$HOME/.profile"
            fi
            [ -f "$HOME/.bashrc" ] && source "$HOME/.bashrc"
            \(trimmed)
            """
            try body.write(to: rcfile, atomically: true, encoding: .utf8)
            return Plan(
                executable: shell,
                args: ["--rcfile", rcfile.path, "-i"],
                envOverrides: [:]
            )

        default:
            // Fish, nushell, etc. — startup script silently dropped. Add per-shell
            // handling here if/when we want to support them.
            return Plan(executable: shell, args: ["-l"], envOverrides: [:])
        }
    }
}
