// GhosttyConfigBuilder.swift
// Maps AppConfig.Terminal + Theme → Ghostty config file and per-surface configuration.

import AppKit
import Foundation
import SwiftUI

enum GhosttyConfigBuilder {
    // MARK: - Cursor style

    /// Cursor style strings as they appear in our app config.
    enum CursorStyle: String, Equatable { case block, beam, underline }

    static func mapCursorStyle(_ raw: String) -> CursorStyle {
        switch raw {
        case "block":     return .block
        case "underline": return .underline
        default:          return .beam
        }
    }

    /// Translate our cursor-style enum into Ghostty's `cursor-style` config value.
    /// Ghostty uses "bar" for what we call "beam".
    static func ghosttyCursorStyleString(_ raw: String) -> String {
        switch mapCursorStyle(raw) {
        case .block:     return "block"
        case .underline: return "underline"
        case .beam:      return "bar"
        }
    }

    // MARK: - Global config file

    /// Build a Ghostty global-config file at `destination`. Returns the URL.
    /// Idempotent: overwrites if exists.
    @discardableResult
    @MainActor
    static func writeGlobalConfigFile(
        cfg: AppConfig.Terminal,
        theme: Theme,
        to destination: URL
    ) throws -> URL {
        let bg = hex(of: theme.color("bg-0"))
        let fg = hex(of: theme.color("fg"))

        var lines: [String] = []
        lines.append("# Generated by Alas — do not edit by hand")
        lines.append("font-family = \"\(cfg.fontFamily)\"")
        lines.append("font-size = \(cfg.fontSize)")
        lines.append("cursor-style = \(ghosttyCursorStyleString(cfg.cursorStyle))")
        lines.append("cursor-style-blink = \(cfg.cursorBlink)")
        lines.append("scrollback-limit = \(cfg.scrollbackLines)")
        // bell-features takes a comma-separated list (system|audio|attention|title|border).
        // Prefix any token with "no-" to disable.
        switch cfg.bell {
        case "off":
            lines.append("bell-features = no-system,no-audio,no-attention,no-title,no-border")
        case "visual":
            lines.append("bell-features = no-system,no-audio,attention,title,border")
        case "sound":
            lines.append("bell-features = system,audio,no-attention,no-title,no-border")
        default: break
        }
        lines.append("background = \(bg)")
        lines.append("foreground = \(fg)")

        // Disable Ghostty's shell integration globally. Every interactive
        // pane in Alas launches through `zmx attach …`, and Ghostty's bash
        // shell-integration rewrites argv in a way `zmx attach` rejects.
        // We accept the loss of Ghostty's OSC-7 cwd auto-tracking as a
        // known v1 tradeoff; revisit if `lastCwd` drift becomes a problem.
        lines.append("shell-integration = none")

        // Defense-in-depth: prevent libghostty from acting on shortcuts Alas owns.
        // SurfaceView.isReservedAppKeyEquivalent already intercepts these before
        // they reach libghostty, but emitting unbinds ensures Ghostty's own
        // keybindings don't fire even if the wrapper changes.
        let alasReservedKeybinds = [
            "cmd+d", "cmd+shift+d", "cmd+w",
            "cmd+alt+left", "cmd+alt+right", "cmd+alt+up", "cmd+alt+down",
            "cmd+ctrl+left", "cmd+ctrl+right", "cmd+ctrl+up", "cmd+ctrl+down",
        ]
        for kb in alasReservedKeybinds {
            lines.append("keybind = \(kb)=unbind")
        }

        let body = lines.joined(separator: "\n") + "\n"
        try Paths.ensureDirectoryExists(destination.deletingLastPathComponent())
        try body.write(to: destination, atomically: true, encoding: .utf8)
        return destination
    }

    // MARK: - Per-surface configuration

    /// Compose a per-surface configuration: cwd, command, env.
    /// `command` is the shell command line including args (Ghostty parses it shell-style).
    static func makeSurfaceConfiguration(
        cwd: URL,
        env: [String: String],
        executable: String,
        args: [String]
    ) -> AlasGhostty.SurfaceConfiguration {
        var c = AlasGhostty.SurfaceConfiguration()
        c.workingDirectory = cwd.path
        c.environment = env
        // Ghostty parses `command` as a shell command line, so any arg with a
        // space (e.g. bash's --rcfile pointing under "Application Support") has
        // to be POSIX-quoted before being joined. Path is the most common
        // offender on macOS.
        c.command = ([executable] + args)
            .map(posixQuote)
            .joined(separator: " ")
        return c
    }

    /// POSIX shell quoting: empty / safe-characters → bare; otherwise wrap in
    /// single quotes, escaping internal single quotes via the standard
    /// `'\''` close-quote / escaped-quote / re-open-quote trick.
    static func posixQuote(_ arg: String) -> String {
        if arg.isEmpty { return "''" }
        let safe = CharacterSet(charactersIn:
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-./:=@%+"
        )
        if arg.unicodeScalars.allSatisfy(safe.contains) {
            return arg
        }
        let escaped = arg.replacingOccurrences(of: "'", with: "'\\''")
        return "'\(escaped)'"
    }

    // MARK: - Private helpers

    @MainActor
    private static func hex(of color: Color) -> String {
        let nsColor = NSColor(color).usingColorSpace(.sRGB) ?? .black
        let r = Int(round(nsColor.redComponent * 255))
        let g = Int(round(nsColor.greenComponent * 255))
        let b = Int(round(nsColor.blueComponent * 255))
        return String(format: "#%02X%02X%02X", r, g, b)
    }
}
