//
//  AgentSkillsView.swift
//  Confab
//
//  Modal for managing SKILL.md-style agent skills (opened via /skills command)
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of Confab.
//
// Confab is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling Confab or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://confab.no>.


import SwiftUI
import UniformTypeIdentifiers

/// Wrapper so .sheet(item:) always gets a fresh identity, avoiding the timing bug
/// where the sheet captures state before editingSkill is set.
private struct SkillEditContext: Identifiable {
    let id = UUID()
    let skill: AgentSkill?   // nil → new, non-nil → edit
}

struct AgentSkillsView: View {
    @Environment(\.dismiss) var dismiss
    @Bindable private var settings = SettingsService.shared

    @State private var editContext: SkillEditContext? = nil
    @State private var statusMessage: LocalizedStringKey? = nil

    private var activeCount: Int { settings.agentSkills.filter { $0.isActive }.count }

    var body: some View {
        VStack(spacing: 0) {
            // Header
            HStack {
                VStack(alignment: .leading, spacing: 2) {
                    Label("Agent Skills", systemImage: "brain")
                        .font(.system(size: 18, weight: .bold))
                    if activeCount > 0 {
                        Text("\(activeCount) active — injected into every conversation")
                            .font(.caption).foregroundStyle(.secondary)
                    }
                }
                Spacer()
                Button { dismiss() } label: {
                    Image(systemName: "xmark.circle.fill")
                        .font(.title2).foregroundStyle(.secondary)
                }
                .buttonStyle(.plain)
                .keyboardShortcut(.escape, modifiers: [])
            }
            .padding(.horizontal, 24).padding(.top, 20).padding(.bottom, 12)

            // Toolbar
            HStack(spacing: 10) {
                Button { editContext = SkillEditContext(skill: nil) } label: {
                    Label("New Skill", systemImage: "plus")
                }
                .buttonStyle(.bordered)

                Button { importSkills() } label: {
                    Label("Import", systemImage: "square.and.arrow.down")
                }
                .buttonStyle(.bordered)

                Button { exportAll() } label: {
                    Label("Export All", systemImage: "square.and.arrow.up")
                }
                .buttonStyle(.bordered)
                .disabled(settings.agentSkills.isEmpty)

                Spacer()

                if let msg = statusMessage {
                    Text(msg).font(.caption).foregroundStyle(.secondary)
                }
            }
            .padding(.horizontal, 24).padding(.bottom, 12)

            Divider()

            if settings.agentSkills.isEmpty {
                VStack(spacing: 12) {
                    Image(systemName: "brain")
                        .font(.system(size: 40)).foregroundStyle(.tertiary)
                    Text("No skills yet")
                        .font(.title3).foregroundStyle(.secondary)
                    Text("Skills are markdown instruction files that teach the AI how to behave. Active skills are automatically injected into the system prompt.")
                        .font(.callout).foregroundStyle(.secondary)
                        .multilineTextAlignment(.center).frame(maxWidth: 380)
                    Text("You can import any SKILL.md file from skill0.io or write your own.")
                        .font(.caption).foregroundStyle(.tertiary)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity).padding()
            } else {
                List {
                    ForEach(settings.agentSkills) { skill in
                        AgentSkillRow(
                            skill: skill,
                            onToggle: { settings.toggleAgentSkill(id: skill.id) },
                            onEdit: { editContext = SkillEditContext(skill: skill) },
                            onExport: { exportOne(skill) },
                            onDelete: { settings.deleteAgentSkill(id: skill.id) },
                            isTrusted: settings.trustSecondBrainSkill,
                            onToggleTrust: { settings.trustSecondBrainSkill.toggle() }
                        )
                    }
                }
                .listStyle(.inset(alternatesRowBackgrounds: true))
            }

            Divider()

            HStack(spacing: 8) {
                Image(systemName: "info.circle").foregroundStyle(.purple).font(.callout)
                Text("Active skills are appended to the system prompt. Toggle them per-skill to control what the AI knows.")
                    .font(.caption).foregroundStyle(.secondary)
                Spacer()
                Button("Done") { dismiss() }
                    .buttonStyle(.borderedProminent)
                    .keyboardShortcut(.return, modifiers: [])
            }
            .padding(.horizontal, 24).padding(.vertical, 12)
        }
        .frame(minWidth: 620, idealWidth: 700, minHeight: 480, idealHeight: 600)
        .sheet(item: $editContext) { ctx in
            AgentSkillEditorSheet(skill: ctx.skill) { saved in
                if ctx.skill != nil { settings.updateAgentSkill(saved) }
                else { settings.addAgentSkill(saved) }
            }
        }
    }

    // MARK: - Import / Export

    private func importSkills() {
        let panel = NSOpenPanel()
        panel.allowsMultipleSelection = true
        panel.canChooseFiles = true
        panel.canChooseDirectories = false
        panel.allowedContentTypes = [
            .plainText,
            UTType(filenameExtension: "md") ?? .plainText,
            .zip
        ]
        panel.message = "Select SKILL.md or .zip skill bundles to import"
        guard panel.runModal() == .OK else { return }

        var imported = 0
        for url in panel.urls {
            let ext = url.pathExtension.lowercased()
            if ext == "zip" {
                if importZip(url) { imported += 1 }
            } else {
                if importMarkdown(url) { imported += 1 }
            }
        }
        if imported > 0 { show("^[\(imported) skill](inflect: true) imported") }
    }

    @discardableResult
    private func importMarkdown(_ url: URL) -> Bool {
        guard let content = try? String(contentsOf: url, encoding: .utf8) else { return false }
        let name = skillName(from: content, fallback: url.deletingPathExtension().lastPathComponent)
        let description = skillDescription(from: content)
        let skill = AgentSkill(name: name, skillDescription: description, content: content, isActive: true)
        if let existing = settings.agentSkills.first(where: { $0.name == name }) {
            var updated = skill; updated.id = existing.id
            settings.updateAgentSkill(updated)
        } else {
            settings.addAgentSkill(skill)
        }
        return true
    }

    @discardableResult
    private func importZip(_ zipURL: URL) -> Bool {
        let tmpDir = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: tmpDir) }

        unzip(zipURL, to: tmpDir)

        // Recursively enumerate all files (zip may contain a subdirectory)
        let allFiles = recursiveFiles(in: tmpDir)

        // Prefer skill.md by name, fall back to any .md
        guard let mdURL = allFiles.first(where: { $0.lastPathComponent.lowercased() == "skill.md" })
                       ?? allFiles.first(where: { $0.pathExtension.lowercased() == "md" }),
              let content = try? String(contentsOf: mdURL, encoding: .utf8)
        else { return false }

        let name = skillName(from: content, fallback: zipURL.deletingPathExtension().lastPathComponent)
        let description = skillDescription(from: content)

        // Find or create skill
        var skill: AgentSkill
        if let existing = settings.agentSkills.first(where: { $0.name == name }) {
            skill = AgentSkill(id: existing.id, name: name, skillDescription: description,
                               content: content, isActive: existing.isActive,
                               createdAt: existing.createdAt, updatedAt: Date())
            settings.updateAgentSkill(skill)
        } else {
            skill = AgentSkill(name: name, skillDescription: description, content: content, isActive: true)
            settings.addAgentSkill(skill)
        }

        // Copy all non-skill-md files to skill directory (flatten hierarchy)
        let dataFiles = allFiles.filter { $0 != mdURL }
        if !dataFiles.isEmpty {
            AgentSkillFilesService.shared.ensureDirectory(for: skill.id)
            for file in dataFiles {
                try? AgentSkillFilesService.shared.addFile(from: file, to: skill.id)
            }
        }

        return true
    }

    /// Recursively list all regular files under a directory
    private func recursiveFiles(in directory: URL) -> [URL] {
        guard let enumerator = FileManager.default.enumerator(
            at: directory,
            includingPropertiesForKeys: [.isRegularFileKey],
            options: [.skipsHiddenFiles])
        else { return [] }
        return (enumerator.allObjects as? [URL] ?? []).filter {
            (try? $0.resourceValues(forKeys: [.isRegularFileKey]))?.isRegularFile == true
        }
    }

    private func exportAll() {
        let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first!
        var exported = 0
        for skill in settings.agentSkills {
            let safeName = skill.name.lowercased()
                .components(separatedBy: .whitespaces).joined(separator: "-")
            let files = AgentSkillFilesService.shared.listFiles(for: skill.id)
            if files.isEmpty {
                let url = downloadsURL.appendingPathComponent(safeName + ".md")
                try? skill.content.write(to: url, atomically: true, encoding: .utf8)
            } else {
                let zipURL = downloadsURL.appendingPathComponent(safeName + ".zip")
                exportAsZip(skill: skill, files: files, to: zipURL)
            }
            exported += 1
        }
        show("^[\(exported) skill](inflect: true) exported to Downloads")
    }

    private func exportOne(_ skill: AgentSkill) {
        let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first!
        let safeName = skill.name.lowercased()
            .components(separatedBy: .whitespaces).joined(separator: "-")
        let files = AgentSkillFilesService.shared.listFiles(for: skill.id)

        if files.isEmpty {
            let url = downloadsURL.appendingPathComponent(safeName + ".md")
            try? skill.content.write(to: url, atomically: true, encoding: .utf8)
            show("Exported \(safeName).md")
        } else {
            let zipURL = downloadsURL.appendingPathComponent(safeName + ".zip")
            exportAsZip(skill: skill, files: files, to: zipURL)
            show("Exported \(safeName).zip")
        }
    }

    private func exportAsZip(skill: AgentSkill, files: [URL], to zipURL: URL) {
        let tmp = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: tmp) }

        try? FileManager.default.createDirectory(at: tmp, withIntermediateDirectories: true)
        try? skill.content.write(to: tmp.appendingPathComponent("skill.md"),
                                  atomically: true, encoding: .utf8)
        for f in files {
            try? FileManager.default.copyItem(at: f, to: tmp.appendingPathComponent(f.lastPathComponent))
        }
        zip(directory: tmp, to: zipURL)
    }

    // MARK: - zip / unzip helpers (use system binaries, always present on macOS)

    private func unzip(_ zipURL: URL, to destDir: URL) {
        try? FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: true)
        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip")
        process.arguments = ["-o", zipURL.path, "-d", destDir.path]
        try? process.run(); process.waitUntilExit()
    }

    private func zip(directory: URL, to destZip: URL) {
        if FileManager.default.fileExists(atPath: destZip.path) {
            try? FileManager.default.removeItem(at: destZip)
        }
        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/usr/bin/zip")
        process.currentDirectoryURL = directory
        process.arguments = ["-r", destZip.path, "."]
        try? process.run(); process.waitUntilExit()
    }

    // MARK: - Helpers

    /// Extract skill name from first # heading, fallback to filename
    private func skillName(from content: String, fallback: String) -> String {
        for line in content.components(separatedBy: .newlines) {
            let trimmed = line.trimmingCharacters(in: .whitespaces)
            if trimmed.hasPrefix("# ") {
                return String(trimmed.dropFirst(2)).trimmingCharacters(in: .whitespaces)
            }
        }
        return fallback.isEmpty ? "Untitled Skill" : fallback
    }

    /// Extract first non-heading, non-empty line as description
    private func skillDescription(from content: String) -> String {
        for line in content.components(separatedBy: .newlines) {
            let trimmed = line.trimmingCharacters(in: .whitespaces)
            if !trimmed.isEmpty && !trimmed.hasPrefix("#") {
                return String(trimmed.prefix(120))
            }
        }
        return ""
    }

    private func show(_ text: LocalizedStringKey) {
        statusMessage = text
        Task { try? await Task.sleep(for: .seconds(3)); statusMessage = nil }
    }
}

// MARK: - Agent Skill Row

private struct AgentSkillRow: View {
    let skill: AgentSkill
    let onToggle: () -> Void
    let onEdit: () -> Void
    let onExport: () -> Void
    let onDelete: () -> Void
    var isTrusted: Bool = false
    var onToggleTrust: (() -> Void)? = nil

    private var fileCount: Int {
        AgentSkillFilesService.shared.listFiles(for: skill.id).count
    }

    var body: some View {
        HStack(spacing: 12) {
            // Active toggle
            Toggle("", isOn: Binding(get: { skill.isActive }, set: { _ in onToggle() }))
                .labelsHidden()
                .help(skill.isActive ? "Skill is active — click to deactivate" : "Skill is inactive — click to activate")

            VStack(alignment: .leading, spacing: 3) {
                HStack(spacing: 6) {
                    Text(skill.name)
                        .font(.system(size: 13, weight: .semibold))
                    if skill.isActive {
                        Text("active")
                            .font(.caption2).foregroundStyle(.white)
                            .padding(.horizontal, 5).padding(.vertical, 2)
                            .background(.purple, in: Capsule())
                    }
                }
                Text(skill.resolvedDescription)
                    .font(.callout).foregroundStyle(.secondary).lineLimit(1)
            }

            Spacer()

            // 2nd Brain: let the user mark bash calls to its helper script as always-trusted,
            // skipping the bash approval dialog. Only shown for this specific skill while active.
            if skill.isActive, skill.isSecondBrainSkill, let onToggleTrust {
                Toggle("Trust", isOn: Binding(get: { isTrusted }, set: { _ in onToggleTrust() }))
                    .toggleStyle(.switch).controlSize(.small)
                    .help("When on, bash commands that call the 2nd Brain helper script run without asking for approval each time.")
            }

            // File count badge
            if fileCount > 0 {
                Label("^[\(fileCount) file](inflect: true)", systemImage: "doc")
                    .font(.caption2).foregroundStyle(.secondary)
                    .padding(.horizontal, 6).padding(.vertical, 3)
                    .background(.blue.opacity(0.1), in: Capsule())
            }

            Text("\(skill.content.count) chars")
                .font(.caption2).foregroundStyle(.tertiary)
                .padding(.horizontal, 6).padding(.vertical, 3)
                .background(.secondary.opacity(0.1), in: Capsule())

            Button(action: onEdit) { Text("Edit").font(.caption) }
                .buttonStyle(.bordered).controlSize(.small)

            Button(action: onExport) { Image(systemName: "square.and.arrow.up") }
                .buttonStyle(.bordered).controlSize(.small)
                .help(fileCount > 0 ? "Export as .zip" : "Export as .md")

            Button(role: .destructive, action: onDelete) { Image(systemName: "trash") }
                .buttonStyle(.bordered).controlSize(.small).help("Delete skill")
        }
        .padding(.vertical, 4)
    }
}

// MARK: - Agent Skills Tab Content (embedded in SettingsView)

struct AgentSkillsTabContent: View {
    @Bindable private var settings = SettingsService.shared

    @State private var editContext: SkillEditContext? = nil
    @State private var statusMessage: LocalizedStringKey? = nil

    private var activeCount: Int { settings.agentSkills.filter { $0.isActive }.count }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            // Intro
            HStack(spacing: 10) {
                Image(systemName: "brain").foregroundStyle(.purple).font(.title3)
                VStack(alignment: .leading, spacing: 2) {
                    Text("Agent Skills")
                        .font(.system(size: 14, weight: .semibold))
                    Text("Markdown instruction files injected into the system prompt. Compatible with SKILL.md format.")
                        .font(.caption).foregroundStyle(.secondary)
                }
            }
            .padding(10)
            .background(.purple.opacity(0.07), in: RoundedRectangle(cornerRadius: 8))

            if activeCount > 0 {
                Label("^[\(activeCount) skill](inflect: true) active — appended to every system prompt", systemImage: "checkmark.circle.fill")
                    .font(.caption).foregroundStyle(.green)
            }

            // Toolbar
            HStack(spacing: 10) {
                Button { editContext = SkillEditContext(skill: nil) } label: {
                    Label("New Skill", systemImage: "plus")
                }
                .buttonStyle(.bordered)

                Button { importSkills() } label: {
                    Label("Import", systemImage: "square.and.arrow.down")
                }
                .buttonStyle(.bordered)

                Button { exportAll() } label: {
                    Label("Export All", systemImage: "square.and.arrow.up")
                }
                .buttonStyle(.bordered)
                .disabled(settings.agentSkills.isEmpty)

                Spacer()
                if let msg = statusMessage {
                    Text(msg).font(.caption).foregroundStyle(.secondary)
                }
            }

            if settings.agentSkills.isEmpty {
                HStack {
                    Spacer()
                    VStack(spacing: 8) {
                        Image(systemName: "brain")
                            .font(.system(size: 32)).foregroundStyle(.tertiary)
                        Text("No skills yet — click New Skill or Import to get started.")
                            .font(.callout).foregroundStyle(.secondary).multilineTextAlignment(.center)
                    }
                    Spacer()
                }
                .padding(.vertical, 32)
            } else {
                VStack(spacing: 0) {
                    ForEach(Array(settings.agentSkills.enumerated()), id: \.element.id) { idx, skill in
                        AgentSkillRow(
                            skill: skill,
                            onToggle: { settings.toggleAgentSkill(id: skill.id) },
                            onEdit: { editContext = SkillEditContext(skill: skill) },
                            onExport: { exportOne(skill) },
                            onDelete: { settings.deleteAgentSkill(id: skill.id) },
                            isTrusted: settings.trustSecondBrainSkill,
                            onToggleTrust: { settings.trustSecondBrainSkill.toggle() }
                        )
                        if idx < settings.agentSkills.count - 1 { Divider() }
                    }
                }
                .background(.background, in: RoundedRectangle(cornerRadius: 10))
                .overlay(RoundedRectangle(cornerRadius: 10).stroke(.quaternary))
            }
        }
        .sheet(item: $editContext) { ctx in
            AgentSkillEditorSheet(skill: ctx.skill) { saved in
                if ctx.skill != nil { settings.updateAgentSkill(saved) }
                else { settings.addAgentSkill(saved) }
            }
        }
    }

    private func importSkills() {
        let panel = NSOpenPanel()
        panel.allowsMultipleSelection = true; panel.canChooseFiles = true
        panel.canChooseDirectories = false
        panel.allowedContentTypes = [
            .plainText,
            UTType(filenameExtension: "md") ?? .plainText,
            .zip
        ]
        panel.message = "Select SKILL.md or .zip skill bundles to import"
        guard panel.runModal() == .OK else { return }
        var imported = 0
        for url in panel.urls {
            let ext = url.pathExtension.lowercased()
            if ext == "zip" {
                if importZip(url) { imported += 1 }
            } else {
                if importMarkdown(url) { imported += 1 }
            }
        }
        if imported > 0 { show("^[\(imported) skill](inflect: true) imported") }
    }

    @discardableResult
    private func importMarkdown(_ url: URL) -> Bool {
        guard let content = try? String(contentsOf: url, encoding: .utf8) else { return false }
        let name = skillName(from: content, fallback: url.deletingPathExtension().lastPathComponent)
        let description = skillDescription(from: content)
        let skill = AgentSkill(name: name, skillDescription: description, content: content, isActive: true)
        if let existing = settings.agentSkills.first(where: { $0.name == name }) {
            var updated = skill; updated.id = existing.id; settings.updateAgentSkill(updated)
        } else {
            settings.addAgentSkill(skill)
        }
        return true
    }

    @discardableResult
    private func importZip(_ zipURL: URL) -> Bool {
        let tmpDir = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: tmpDir) }

        unzip(zipURL, to: tmpDir)

        // Recursively enumerate all files (zip may contain a subdirectory)
        let allFiles = recursiveFiles(in: tmpDir)

        guard let mdURL = allFiles.first(where: { $0.lastPathComponent.lowercased() == "skill.md" })
                       ?? allFiles.first(where: { $0.pathExtension.lowercased() == "md" }),
              let content = try? String(contentsOf: mdURL, encoding: .utf8)
        else { return false }

        let name = skillName(from: content, fallback: zipURL.deletingPathExtension().lastPathComponent)
        let description = skillDescription(from: content)

        var skill: AgentSkill
        if let existing = settings.agentSkills.first(where: { $0.name == name }) {
            skill = AgentSkill(id: existing.id, name: name, skillDescription: description,
                               content: content, isActive: existing.isActive,
                               createdAt: existing.createdAt, updatedAt: Date())
            settings.updateAgentSkill(skill)
        } else {
            skill = AgentSkill(name: name, skillDescription: description, content: content, isActive: true)
            settings.addAgentSkill(skill)
        }

        let dataFiles = allFiles.filter { $0 != mdURL }
        if !dataFiles.isEmpty {
            AgentSkillFilesService.shared.ensureDirectory(for: skill.id)
            for file in dataFiles {
                try? AgentSkillFilesService.shared.addFile(from: file, to: skill.id)
            }
        }

        return true
    }

    /// Recursively list all regular files under a directory
    private func recursiveFiles(in directory: URL) -> [URL] {
        guard let enumerator = FileManager.default.enumerator(
            at: directory,
            includingPropertiesForKeys: [.isRegularFileKey],
            options: [.skipsHiddenFiles])
        else { return [] }
        return (enumerator.allObjects as? [URL] ?? []).filter {
            (try? $0.resourceValues(forKeys: [.isRegularFileKey]))?.isRegularFile == true
        }
    }

    private func exportAll() {
        let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first!
        var exported = 0
        for skill in settings.agentSkills {
            let safeName = skill.name.lowercased()
                .components(separatedBy: .whitespaces).joined(separator: "-")
            let files = AgentSkillFilesService.shared.listFiles(for: skill.id)
            if files.isEmpty {
                let url = downloadsURL.appendingPathComponent(safeName + ".md")
                try? skill.content.write(to: url, atomically: true, encoding: .utf8)
            } else {
                let zipURL = downloadsURL.appendingPathComponent(safeName + ".zip")
                exportAsZip(skill: skill, files: files, to: zipURL)
            }
            exported += 1
        }
        show("^[\(exported) skill](inflect: true) exported to Downloads")
    }

    private func exportOne(_ skill: AgentSkill) {
        let downloadsURL = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first!
        let safeName = skill.name.lowercased()
            .components(separatedBy: .whitespaces).joined(separator: "-")
        let files = AgentSkillFilesService.shared.listFiles(for: skill.id)

        if files.isEmpty {
            let url = downloadsURL.appendingPathComponent(safeName + ".md")
            try? skill.content.write(to: url, atomically: true, encoding: .utf8)
            show("Exported \(safeName).md")
        } else {
            let zipURL = downloadsURL.appendingPathComponent(safeName + ".zip")
            exportAsZip(skill: skill, files: files, to: zipURL)
            show("Exported \(safeName).zip")
        }
    }

    private func exportAsZip(skill: AgentSkill, files: [URL], to zipURL: URL) {
        let tmp = FileManager.default.temporaryDirectory
            .appendingPathComponent(UUID().uuidString, isDirectory: true)
        defer { try? FileManager.default.removeItem(at: tmp) }

        try? FileManager.default.createDirectory(at: tmp, withIntermediateDirectories: true)
        try? skill.content.write(to: tmp.appendingPathComponent("skill.md"),
                                  atomically: true, encoding: .utf8)
        for f in files {
            try? FileManager.default.copyItem(at: f, to: tmp.appendingPathComponent(f.lastPathComponent))
        }
        zip(directory: tmp, to: zipURL)
    }

    private func unzip(_ zipURL: URL, to destDir: URL) {
        try? FileManager.default.createDirectory(at: destDir, withIntermediateDirectories: true)
        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/usr/bin/unzip")
        process.arguments = ["-o", zipURL.path, "-d", destDir.path]
        try? process.run(); process.waitUntilExit()
    }

    private func zip(directory: URL, to destZip: URL) {
        if FileManager.default.fileExists(atPath: destZip.path) {
            try? FileManager.default.removeItem(at: destZip)
        }
        let process = Process()
        process.executableURL = URL(fileURLWithPath: "/usr/bin/zip")
        process.currentDirectoryURL = directory
        process.arguments = ["-r", destZip.path, "."]
        try? process.run(); process.waitUntilExit()
    }

    private func skillName(from content: String, fallback: String) -> String {
        for line in content.components(separatedBy: .newlines) {
            let t = line.trimmingCharacters(in: .whitespaces)
            if t.hasPrefix("# ") { return String(t.dropFirst(2)).trimmingCharacters(in: .whitespaces) }
        }
        return fallback.isEmpty ? "Untitled Skill" : fallback
    }

    private func skillDescription(from content: String) -> String {
        for line in content.components(separatedBy: .newlines) {
            let t = line.trimmingCharacters(in: .whitespaces)
            if !t.isEmpty && !t.hasPrefix("#") { return String(t.prefix(120)) }
        }
        return ""
    }

    private func show(_ text: LocalizedStringKey) {
        statusMessage = text
        Task { try? await Task.sleep(for: .seconds(3)); statusMessage = nil }
    }
}

#Preview { AgentSkillsView() }
