import SwiftUI
import Combine

@MainActor
protocol TerminalSessionFactoryType {
    func makeSession(
        fontSize: CGFloat,
        theme: TerminalTheme,
        shellPath: String,
        initialDirectory: String?
    ) -> TerminalSession
}

struct LiveTerminalSessionFactory: TerminalSessionFactoryType {
    func makeSession(
        fontSize: CGFloat,
        theme: TerminalTheme,
        shellPath: String,
        initialDirectory: String?
    ) -> TerminalSession {
        TerminalSession(
            fontSize: fontSize,
            theme: theme,
            shellPath: shellPath,
            initialDirectory: initialDirectory
        )
    }
}

@MainActor
final class WorkspaceController: ObservableObject {
    let id: WorkspaceID
    let createdAt: Date

    @Published private(set) var name: String
    @Published private(set) var hotkey: HotkeyBinding?
    @Published private(set) var tabs: [TerminalSession] = []
    @Published private(set) var activeTabIndex: Int = 0

    private let sessionFactory: TerminalSessionFactoryType
    private let settingsProvider: TerminalSessionConfigurationProviding
    private var titleObservers: [UUID: AnyCancellable] = [:]

    init(
        summary: WorkspaceSummary,
        sessionFactory: TerminalSessionFactoryType,
        settingsProvider: TerminalSessionConfigurationProviding,
        bootstrapDefaultTab: Bool = true
    ) {
        self.id = summary.id
        self.name = summary.name
        self.createdAt = summary.createdAt
        self.hotkey = summary.hotkey
        self.sessionFactory = sessionFactory
        self.settingsProvider = settingsProvider

        if bootstrapDefaultTab {
            newTab()
        }
    }

    convenience init(summary: WorkspaceSummary) {
        self.init(
            summary: summary,
            sessionFactory: LiveTerminalSessionFactory(),
            settingsProvider: AppSettingsController.shared
        )
    }

    var summary: WorkspaceSummary {
        WorkspaceSummary(id: id, name: name, createdAt: createdAt, hotkey: hotkey)
    }

    var state: WorkspaceState {
        WorkspaceState(
            id: id,
            name: name,
            tabs: tabs.map { WorkspaceTabState(id: $0.id, title: $0.title) },
            activeTabID: activeTab?.id
        )
    }

    var activeTab: TerminalSession? {
        guard tabs.indices.contains(activeTabIndex) else { return nil }
        return tabs[activeTabIndex]
    }

    var activeTitle: String {
        activeTab?.title ?? "shell"
    }

    func rename(to updatedName: String) {
        let trimmed = updatedName.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !trimmed.isEmpty, trimmed != name else { return }
        name = trimmed
    }

    func updateHotkey(_ updatedHotkey: HotkeyBinding?) {
        guard hotkey != updatedHotkey else { return }
        hotkey = updatedHotkey
    }

    func newTab() {
        let config = settingsProvider.terminalSessionConfiguration
        let session = sessionFactory.makeSession(
            fontSize: config.fontSize,
            theme: config.theme,
            shellPath: config.shellPath,
            initialDirectory: activeTab?.currentDirectory
        )

        titleObservers[session.id] = session.$title
            .receive(on: RunLoop.main)
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }

        tabs.append(session)
        activeTabIndex = tabs.count - 1
    }

    func closeTab(at index: Int) {
        guard tabs.indices.contains(index) else { return }

        let session = tabs.remove(at: index)
        titleObservers.removeValue(forKey: session.id)
        session.terminate()

        if tabs.isEmpty {
            newTab()
        } else if activeTabIndex >= tabs.count {
            activeTabIndex = tabs.count - 1
        }
    }

    func closeActiveTab() {
        closeTab(at: activeTabIndex)
    }

    func switchToTab(at index: Int) {
        guard tabs.indices.contains(index) else { return }
        activeTabIndex = index
    }

    func switchToTab(id: UUID) {
        guard let index = tabs.firstIndex(where: { $0.id == id }) else { return }
        activeTabIndex = index
    }

    func nextTab() {
        guard tabs.count > 1 else { return }
        activeTabIndex = (activeTabIndex + 1) % tabs.count
    }

    func previousTab() {
        guard tabs.count > 1 else { return }
        activeTabIndex = (activeTabIndex - 1 + tabs.count) % tabs.count
    }

    func detachTab(at index: Int) -> TerminalSession? {
        guard tabs.indices.contains(index) else { return nil }

        let session = tabs.remove(at: index)
        titleObservers.removeValue(forKey: session.id)

        if tabs.isEmpty {
            newTab()
        } else if activeTabIndex >= tabs.count {
            activeTabIndex = tabs.count - 1
        }

        return session
    }

    func detachActiveTab() -> TerminalSession? {
        detachTab(at: activeTabIndex)
    }

    func updateAllFontSizes(_ size: CGFloat) {
        for tab in tabs {
            tab.updateFontSize(size)
        }
    }

    func updateAllThemes(_ theme: TerminalTheme) {
        for tab in tabs {
            tab.applyTheme(theme)
        }
    }
}
