import AppKitExtension
import Client
import ComposableArchitecture
import ConversationServiceProvider
import SwiftUI
import Toast
import XcodeInspector
import SharedUIComponents
import Logger
import SystemUtils

struct ChatSection: View {
    @AppStorage(\.autoAttachChatToXcode) var autoAttachChatToXcode
    @AppStorage(\.enableFixError) var enableFixError
    @AppStorage(\.enableSubagent) var enableSubagent
    @AppStorage(\.enableAutoApproval) var enableAutoApproval
    @AppStorage(\.trustToolAnnotations) var trustToolAnnotations
    @ObservedObject private var featureFlags = FeatureFlagManager.shared
    @ObservedObject private var copilotPolicy = CopilotPolicyManager.shared

    var body: some View {
        SettingsSection(title: "Chat Settings") {
            // Copilot instructions - .github/copilot-instructions.md
            CopilotInstructionSetting()
                .padding(SettingsToggle.defaultPadding)

            Divider()

            // Custom Instructions - .github/instructions/*.instructions.md
            PromptFileSetting(promptType: .instructions)
                .padding(SettingsToggle.defaultPadding)

            Divider()

            if featureFlags.isEditorPreviewEnabled {
                // Custom Prompts - .github/prompts/*.prompt.md
                PromptFileSetting(promptType: .prompt)
                    .padding(SettingsToggle.defaultPadding)

                Divider()

                if featureFlags.isAgentModeEnabled && copilotPolicy.isCustomAgentEnabled {
                    // Custom Agents - .github/agents/*.agent.md
                    AgentFileSetting(promptType: .agent)
                        .padding(SettingsToggle.defaultPadding)

                    Divider()

                    // SubAgent toggle
                    SettingsToggle(
                        title: "Enable Subagent",
                        subtitle: "Allows Copilot Agent mode to call custom agents as subagent. Requires GitHub Copilot for Xcode restart to take effect.",
                        isOn: Binding(
                            get: { enableSubagent && copilotPolicy.isSubagentEnabled },
                            set: { if copilotPolicy.isSubagentEnabled { enableSubagent = $0 } }
                        ),
                        badge: copilotPolicy.isSubagentEnabled 
                            ? nil 
                            : .disabledByPolicy(feature: "Subagents", isPlural: true)
                    )
                    .disabled(!copilotPolicy.isSubagentEnabled)

                    Divider()
                }
            }
            
            // Auto Attach toggle
            SettingsToggle(
                title: "Auto-attach Chat Window to Xcode",
                isOn: $autoAttachChatToXcode
            )

            Divider()
            
            // Fix error toggle
            SettingsToggle(
                title: "Quick fix for error", 
                isOn: $enableFixError
            )
            
            Divider()

            // Response language picker
            ResponseLanguageSetting()
                .padding(SettingsToggle.defaultPadding)
            
            Divider()
            
            // Font Size
            FontSizeSetting()
                .padding(SettingsToggle.defaultPadding)
            
            if featureFlags.isAgentModeEnabled {
                Divider()
                
                // Agent Max Tool Calling Requests
                AgentMaxToolCallLoopSetting()
                    .padding(SettingsToggle.defaultPadding)

                Divider()

                // Auto Approval Toggles
                AgentAutoApprovalSetting()

                Divider()
                    
                AgentTrustToolAnnotationsSetting()
            }
        }
    }
}

struct ResponseLanguageSetting: View {
    @AppStorage(\.chatResponseLocale) var chatResponseLocale

    // Locale codes mapped to language display names
    // reference: https://code.visualstudio.com/docs/configure/locales#_available-locales
    private let localeLanguageMap: [String: String] = [
        "en": "English",
        "zh-cn": "Chinese, Simplified",
        "zh-tw": "Chinese, Traditional",
        "fr": "French",
        "de": "German",
        "it": "Italian",
        "es": "Spanish",
        "ja": "Japanese",
        "ko": "Korean",
        "ru": "Russian",
        "pt-br": "Portuguese (Brazil)",
        "tr": "Turkish",
        "pl": "Polish",
        "cs": "Czech",
        "hu": "Hungarian",
    ]

    var selectedLanguage: String {
        if chatResponseLocale == "" {
            return "English"
        }

        return localeLanguageMap[chatResponseLocale] ?? "English"
    }

    // Display name to locale code mapping (for the picker UI)
    var sortedLanguageOptions: [(displayName: String, localeCode: String)] {
        localeLanguageMap.map { (displayName: $0.value, localeCode: $0.key) }
            .sorted { $0.displayName < $1.displayName }
    }

    var body: some View {
        WithPerceptionTracking {
            HStack {
                VStack(alignment: .leading) {
                    Text("Response Language")
                        .font(.body)
                    Text("This change applies only to new chat sessions. Existing ones won't be impacted.")
                        .font(.footnote)
                }

                Spacer()

                Picker("", selection: $chatResponseLocale) {
                    ForEach(sortedLanguageOptions, id: \.localeCode) { option in
                        Text(option.displayName).tag(option.localeCode)
                    }
                }
                .frame(maxWidth: 200, alignment: .trailing)
            }
        }
    }
}

struct FontSizeSetting: View {
    static let defaultSliderThumbRadius: CGFloat = Font.body.builtinSize
    
    @AppStorage(\.chatFontSize) var chatFontSize
    @ScaledMetric(relativeTo: .body) var scaledPadding: CGFloat = 100
    
    @State private var sliderValue: Double = 0
    @State private var textWidth: CGFloat = 0
    @State private var sliderWidth: CGFloat = 0
    
    @StateObject private var fontScaleManager: FontScaleManager = .shared
    
    var maxSliderValue: Double {
        FontScaleManager.maxScale * 100
    }
    
    var minSliderValue: Double {
        FontScaleManager.minScale * 100
    }
    
    var defaultSliderValue: Double {
        FontScaleManager.defaultScale * 100
    }
    
    var sliderFontSize: Double {
        chatFontSize * sliderValue / 100
    }
    
    var maxScaleFontSize: Double {
        FontScaleManager.maxScale * chatFontSize
    }
    
    var body: some View {
        WithPerceptionTracking {
            HStack {
                VStack(alignment: .leading) {
                    Text("Font Size")
                        .font(.body)
                    Text("Use the slider to set the preferred size.")
                        .font(.footnote)
                }
                
                Spacer()
                
                VStack(alignment: .leading, spacing: 0) {
                    HStack(alignment: .center, spacing: 8) {
                        Text("A")
                            .font(.system(size: sliderFontSize))
                            .frame(width: maxScaleFontSize)
                        
                        Slider(value: $sliderValue, in: minSliderValue...maxSliderValue, step: 10) { _ in
                            fontScaleManager.setFontScale(sliderValue / 100)
                        }
                        .background(
                            GeometryReader { geometry in 
                                Color.clear
                                    .onAppear {
                                        sliderWidth = geometry.size.width
                                    }
                            }
                        )
                        
                        Text("\(Int(sliderValue))%")
                            .font(.body)
                            .foregroundColor(.primary)
                            .frame(width: 40, alignment: .center)
                    }
                    .frame(height: maxScaleFontSize)
                    
                    Text("Default")
                        .font(.caption)
                        .foregroundColor(.primary)
                        .background(
                            GeometryReader { geometry in 
                                Color.clear
                                    .onAppear {
                                        textWidth = geometry.size.width
                                    }
                            }
                        )
                        .padding(.leading, calculateDefaultMarkerXPosition() + 6)
                        .onHover {
                            if $0 {
                                NSCursor.pointingHand.push()
                            } else {
                                NSCursor.pop()
                            }
                        }
                        .onTapGesture {
                            fontScaleManager.resetFontScale()
                        }
                }
                .frame(width: 350, height: 35)
            }
            .onAppear {
                sliderValue = fontScaleManager.currentScale * 100
            }
            .onChange(of: fontScaleManager.currentScale) {
                // Use rounded value for floating-point precision issue
                sliderValue = round($0 * 10) / 10 * 100
            }
        }
    }
    
    private func calculateDefaultMarkerXPosition() -> CGFloat {
        let sliderRange = maxSliderValue - minSliderValue
        let normalizedPosition = (defaultSliderValue - minSliderValue) / sliderRange
        
        let usableWidth = sliderWidth - (Self.defaultSliderThumbRadius * 2)
        
        let markerPosition = Self.defaultSliderThumbRadius + (CGFloat(normalizedPosition) * usableWidth)
        
        return markerPosition - textWidth / 2 + maxScaleFontSize
    }
}

struct AgentMaxToolCallLoopSetting: View {
    @AppStorage(\.agentMaxToolCallingLoop) var agentMaxToolCallingLoop
    @State private var numberInput: String = ""
    @State private var debounceTimer: Timer?
    
    private static let debounceDelay: TimeInterval = 0.5
    
    var body: some View {
        WithPerceptionTracking {
            HStack {
                VStack(alignment: .leading) {
                    Text("Agent Max Requests")
                        .font(.body)
                    Text("Sets the maximum number of tool call requests Copilot can make in a single agent turn.")
                        .font(.footnote)
                }
                
                Spacer()
                
                TextField("", text: $numberInput)
                    .textFieldStyle(.roundedBorder)
                    .frame(minWidth: 40, maxWidth: 120)
                    .fixedSize(horizontal: true, vertical: false)
                    .onChange(of: numberInput) { newValue in
                        if newValue.isEmpty { return }
                        
                        guard let number = Int(newValue.filter { $0.isNumber }), number > 0 else {
                            numberInput = ""
                            return
                        }
                        
                        numberInput = "\(number)"
                        
                        debounceTimer?.invalidate()
                        debounceTimer = Timer.scheduledTimer(
                            withTimeInterval: Self.debounceDelay,
                            repeats: false
                        ) { _ in
                            agentMaxToolCallingLoop = number
                            DistributedNotificationCenter
                                .default()
                                .post(name: .githubCopilotAgentMaxToolCallingLoopDidChange, object: nil)
                        }
                    }
            }
            .onAppear {
                numberInput = "\(agentMaxToolCallingLoop)"
            }
            .onDisappear {
                // Flush before invalidating
                if let timer = debounceTimer, timer.isValid {
                    timer.fire()
                }
                
                debounceTimer?.invalidate()
                debounceTimer = nil
            }
        }
    }
}

struct CopilotInstructionSetting: View {
    @State var isGlobalInstructionsViewOpen = false
    @Environment(\.toast) var toast

    var body: some View {
        WithPerceptionTracking {
            HStack {
                VStack(alignment: .leading) {
                    Text("Copilot Instructions")
                        .font(.body)
                    Text("Configure `.github/copilot-instructions.md` to apply to all chat requests.")
                        .font(.footnote)
                }

                Spacer()

                Button("Current Workspace") {
                    openCustomInstructions()
                }

                Button("Global") {
                    isGlobalInstructionsViewOpen = true
                }
            }
            .sheet(isPresented: $isGlobalInstructionsViewOpen) {
                GlobalInstructionsView(isOpen: $isGlobalInstructionsViewOpen)
            }
        }
    }

    func openCustomInstructions() {
        Task {
            guard let projectURL = await getCurrentProjectURL() else {
                toast("No active workspace found", .error)
                return
            }

            let configFile = projectURL.appendingPathComponent(".github/copilot-instructions.md")

            // If the file doesn't exist, create one with a proper structure
            if !FileManager.default.fileExists(atPath: configFile.path) {
                do {
                    // Create directory if it doesn't exist using reusable helper
                    let gitHubDir = projectURL.appendingPathComponent(".github")
                    try ensureDirectoryExists(at: gitHubDir)

                    // Create empty file
                    try "".write(to: configFile, atomically: true, encoding: .utf8)
                } catch {
                    toast("Failed to create config file .github/copilot-instructions.md: \(error)", .error)
                }
            }

            if FileManager.default.fileExists(atPath: configFile.path) {
                NSWorkspace.shared.open(configFile)
            }
        }
    }
}

struct PromptFileSetting: View {
    let promptType: PromptType
    @State private var isCreateSheetPresented = false
    @Environment(\.toast) var toast

    var body: some View {
        WithPerceptionTracking {
            HStack {
                VStack(alignment: .leading) {
                    Text(promptType.settingTitle)
                        .font(.body)
                    Text(
                        (try? AttributedString(markdown: promptType.description)) ?? AttributedString(
                            promptType.description
                        )
                    )
                    .font(.footnote)
                }

                Spacer()

                Button("Create") {
                    isCreateSheetPresented = true
                }

                Button("Open \(promptType.directoryName.capitalized) Folder") {
                    openDirectory()
                }
            }
            .sheet(isPresented: $isCreateSheetPresented) {
                CreateCustomCopilotFileView(
                    promptType: promptType,
                    editorPluginVersion: SystemUtils.editorPluginVersionString,
                    getCurrentProjectURL: { await getCurrentProjectURL() },
                    onSuccess: { message in
                        toast(message, .info)
                    },
                    onError: { message in
                        toast(message, .error)
                    }
                )
            }
        }
    }

    private func openDirectory() {
        Task {
            guard let projectURL = await getCurrentProjectURL() else {
                toast("No active workspace found", .error)
                return
            }

            let directory = promptType.getDirectoryPath(projectURL: projectURL)

            do {
                try ensureDirectoryExists(at: directory)
                NSWorkspace.shared.open(directory)
            } catch {
                toast("Failed to create \(promptType.directoryName) directory: \(error)", .error)
            }
        }
    }
}

struct AgentFileSetting: View {
    let promptType: PromptType
    @State private var isCreateSheetPresented = false
    @Environment(\.toast) var toast

    var body: some View {
        WithPerceptionTracking {
            HStack {
                VStack(alignment: .leading) {
                    Text(promptType.settingTitle)
                        .font(.body)
                    Text(
                        (try? AttributedString(markdown: promptType.description)) ?? AttributedString(
                            promptType.description
                        )
                    )
                    .font(.footnote)
                }

                Spacer()

                Button("Create") {
                    isCreateSheetPresented = true
                }

                Button("Browse \(promptType.displayName)s") {
                    openDirectory()
                }
            }
            .sheet(isPresented: $isCreateSheetPresented) {
                CreateCustomCopilotFileView(
                    promptType: promptType,
                    editorPluginVersion: SystemUtils.editorPluginVersionString,
                    getCurrentProjectURL: { await getCurrentProjectURL() },
                    onSuccess: { message in
                        toast(message, .info)
                    },
                    onError: { message in
                        toast(message, .error)
                    }
                )
            }
        }
    }

    private func openDirectory() {
        Task {
            guard let projectURL = await getCurrentProjectURL() else {
                toast("No active workspace found", .error)
                return
            }

            let directory = promptType.getDirectoryPath(projectURL: projectURL)

            do {
                try ensureDirectoryExists(at: directory)

                // Open file picker for .agent.md files
                await MainActor.run {
                    let panel = NSOpenPanel()
                    panel.allowedContentTypes = [.init(filenameExtension: "agent.md") ?? .plainText]
                    panel.allowsMultipleSelection = false
                    panel.canChooseFiles = true
                    panel.canChooseDirectories = false
                    panel.level = .modalPanel
                    panel.directoryURL = directory
                    panel.message = "Select an existing agent file"
                    panel.prompt = "Select"
                    panel.showsHiddenFiles = false

                    panel.allowsOtherFileTypes = false
                    panel.isExtensionHidden = false

                    panel.begin { response in
                        if response == .OK, let selectedURL = panel.url {
                            // If the file doesn't exist, create it
                            if !FileManager.default.fileExists(atPath: selectedURL.path) {
                                do {
                                    // Create empty agent file with basic structure
                                    let template = promptType.defaultTemplate
                                    try template.write(to: selectedURL, atomically: true, encoding: .utf8)
                                } catch {
                                    toast("Failed to create agent file: \(error)", .error)
                                    return
                                }
                            }

                            // Open the file in Xcode
                            NSWorkspace.openFileInXcode(fileURL: selectedURL)
                        }
                    }
                }
            } catch {
                toast("Failed to create \(promptType.directoryName) directory: \(error)", .error)
            }
        }
    }
}

struct AgentAutoApprovalSetting: View {
    @AppStorage(\.enableAutoApproval) var enableAutoApproval
    @ObservedObject private var featureFlags = FeatureFlagManager.shared
    @ObservedObject private var copilotPolicy = CopilotPolicyManager.shared

    var autoApprovalPolicyEnabled : Bool {
        copilotPolicy.isAgentModeAutoApprovalEnabled && featureFlags.isEditorPreviewEnabled && featureFlags.isAgenModeAutoApprovalEnabled
    }

    var body: some View {
        WithPerceptionTracking {
            SettingsToggle(
                title: "Auto Approval",
                subtitle: "Controls whether tool calls should be automatically approved.",
                isOn: Binding(
                    get: { enableAutoApproval && autoApprovalPolicyEnabled },
                    set: { if autoApprovalPolicyEnabled { enableAutoApproval = $0 } }
                ),
                badge: autoApprovalPolicyEnabled ? nil : .disabledByPolicy(feature: "Auto approval")
            )
            .disabled(!autoApprovalPolicyEnabled)
            .onChange(of: enableAutoApproval) { _ in
                DistributedNotificationCenter
                    .default()
                    .post(name: .githubCopilotAgentAutoApprovalDidChange, object: nil)
            }
        }
    }
}

struct AgentTrustToolAnnotationsSetting: View {
    @AppStorage(\.trustToolAnnotations) var trustToolAnnotations
    @ObservedObject private var featureFlags = FeatureFlagManager.shared
    @ObservedObject private var copilotPolicy = CopilotPolicyManager.shared

    var autoApprovalPolicyEnabled : Bool {
        copilotPolicy.isAgentModeAutoApprovalEnabled && featureFlags.isEditorPreviewEnabled && featureFlags.isAgenModeAutoApprovalEnabled
    }

    var body: some View {
        WithPerceptionTracking {
            SettingsToggle(
                title: "Trust MCP Tool Annotations",
                subtitle: "If enabled, Copilot will use tool annotations to decide whether to automatically approve readonly MCP tool calls.",
                isOn: Binding(
                    get: { trustToolAnnotations && autoApprovalPolicyEnabled },
                    set: { if autoApprovalPolicyEnabled { trustToolAnnotations = $0 } }
                ),
                badge: autoApprovalPolicyEnabled ? nil : .disabledByPolicy(feature: "Auto approval")
            )
            .disabled(!autoApprovalPolicyEnabled)
            .onChange(of: trustToolAnnotations) { _ in
                DistributedNotificationCenter
                    .default()
                    .post(name: .githubCopilotAgentTrustToolAnnotationsDidChange, object: nil)
            }
        }
    }
}

#Preview {
    ChatSection()
        .frame(width: 600)
}
