// Creating, duplicating, renaming, recolouring and deleting a settings profile — one sheet for
// all five (design/client-settings-profiles.md §5.1).
//
// It replaced a menu of four separate actions, three of which raised their own bare text alert
// and the fourth a submenu of colour NAMES with no colour anywhere on it. Making a profile then
// meant: name it in an alert, find it again in the scope menu, open a submenu, and pick "Amber"
// on faith. The two things a profile has — a name and a colour — are decided together here, with
// a live chip showing exactly what the host cards will render.

import PunktfunkKit
import SwiftUI

/// What the sheet was opened to do. Carrying the seed values rather than an id keeps the sheet
/// free of "which store do I read to find out what I'm editing" — the caller already knows.
struct ProfileDraft: Identifiable {
    /// nil = creating (a blank profile, or a duplicate); set = editing that profile.
    var editingID: String?
    var name: String
    var accent: String?
    /// What a newly created profile starts with. Empty for a blank one; the source's overrides
    /// for a duplicate, which is the whole point of duplicating.
    var overrides = SettingsOverlay()
    var title: String
    var accept: String

    var id: String { editingID ?? "new-\(title)" }

    static func create() -> ProfileDraft {
        ProfileDraft(name: "", accent: nil, title: "New Profile", accept: "Create")
    }

    static func edit(_ profile: StreamProfile) -> ProfileDraft {
        ProfileDraft(
            editingID: profile.id, name: profile.name, accent: profile.accent,
            title: "Edit Profile", accept: "Save")
    }

    static func duplicate(_ profile: StreamProfile, name: String) -> ProfileDraft {
        ProfileDraft(
            name: name, accent: profile.accent, overrides: profile.overrides,
            title: "Duplicate Profile", accept: "Duplicate")
    }
}

struct ProfileEditorSheet: View {
    @Environment(\.dismiss) private var dismiss
    @ObservedObject private var profiles = ProfileStore.shared

    let draft: ProfileDraft
    /// Where the settings surface should be pointing afterwards — at the profile that was just
    /// created, so you land in the layer you made rather than back on the defaults.
    let onScope: (SettingsScope) -> Void

    @State private var name: String
    @State private var accent: String?

    init(draft: ProfileDraft, onScope: @escaping (SettingsScope) -> Void) {
        self.draft = draft
        self.onScope = onScope
        _name = State(initialValue: draft.name)
        _accent = State(initialValue: draft.accent)
    }

    var body: some View {
        #if os(macOS)
        VStack(spacing: 0) {
            form
            HStack {
                Button("Cancel", role: .cancel) { dismiss() }
                    .keyboardShortcut(.cancelAction)
                Spacer()
                Button(draft.accept) { commit() }
                    .glassProminentButtonStyle()
                    .keyboardShortcut(.defaultAction)
                    .disabled(!isNameAcceptable)
            }
            .padding(16)
        }
        .frame(width: 420)
        .fixedSize(horizontal: false, vertical: true)
        #else
        NavigationStack {
            form
                .navigationTitle(draft.title)
                #if os(iOS)
                .navigationBarTitleDisplayMode(.inline)
                #endif
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Cancel") { dismiss() }
                    }
                    ToolbarItem(placement: .confirmationAction) {
                        Button(draft.accept) { commit() }
                            .disabled(!isNameAcceptable)
                    }
                }
        }
        .presentationDetents([.medium, .large])
        .presentationDragIndicator(.visible)
        #endif
    }

    // MARK: - Form

    private var form: some View {
        Form {
            Section {
                preview
                    .frame(maxWidth: .infinity)
                    .padding(.vertical, 8)
                    .listRowInsets(EdgeInsets())
                    .listRowBackground(Color.clear)
            }
            Section {
                TextField("Name", text: $name, prompt: Text("Name — e.g. Game, Work, Travel"))
                    #if os(iOS)
                    .textInputAutocapitalization(.words)
                    #endif
            } footer: {
                Text(nameFootnote)
                    .font(.geist(12, relativeTo: .caption))
                    .foregroundStyle(duplicateName ? Color.red : Color.secondary)
            }
            Section {
                swatches
                    .listRowInsets(EdgeInsets(top: 12, leading: 16, bottom: 12, trailing: 16))
            } header: {
                Text("Color")
            } footer: {
                Text("Tints this profile's chip on host cards and in the stream overlay.")
                    .font(.geist(12, relativeTo: .caption))
                    .foregroundStyle(.secondary)
            }
        }
        .formStyle(.grouped)
        #if os(macOS)
        .frame(minHeight: 420)
        #endif
    }

    /// The profile exactly as a host card will show it — the answer to "what am I choosing?",
    /// which a list of colour names never gave.
    private var preview: some View {
        let shown = name.trimmingCharacters(in: .whitespaces)
        return ProfileChip(
            profile: StreamProfile(name: shown.isEmpty ? "Profile" : shown, accent: accent),
            size: 13, prominent: true)
            .opacity(shown.isEmpty ? 0.5 : 1)
            .animation(.easeOut(duration: 0.15), value: accent)
    }

    /// The palette, as colours. `nil` is the brand default and leads, so "no colour" is a choice
    /// on the same shelf as the rest rather than the absence of one.
    private var swatches: some View {
        LazyVGrid(
            columns: [GridItem(.adaptive(minimum: 44), spacing: 14, alignment: .center)],
            spacing: 14
        ) {
            swatch(nil, label: "Default")
            ForEach(ProfileAccent.palette) { option in
                swatch(option.hex, label: option.name)
            }
        }
    }

    private func swatch(_ hex: String?, label: String) -> some View {
        let selected = accent?.caseInsensitiveCompare(hex ?? "") == .orderedSame
            || (accent == nil && hex == nil)
        let color = hex.flatMap { Color(hex: $0) } ?? .brand
        return Button {
            accent = hex
        } label: {
            ZStack {
                Circle()
                    .fill(color)
                    .frame(width: 30, height: 30)
                if selected {
                    Image(systemName: "checkmark")
                        .font(.footnote.weight(.bold))
                        .foregroundStyle(.white)
                    // A ring OUTSIDE the swatch as well as the checkmark: the tick alone washes
                    // out on the pale hues and the ring alone is easy to miss at this size.
                    Circle()
                        .strokeBorder(color, lineWidth: 2)
                        .frame(width: 40, height: 40)
                }
            }
            // 30pt is under the touch minimum; the cell is the target, not the dot.
            .frame(width: 44, height: 44)
            .contentShape(Circle())
        }
        .buttonStyle(.plain)
        .accessibilityLabel(label)
        .accessibilityAddTraits(selected ? [.isButton, .isSelected] : .isButton)
        .help(label)
    }

    // MARK: - Validation + commit

    private var trimmedName: String { name.trimmingCharacters(in: .whitespaces) }

    /// Case-insensitively unique — two "Work"s make every menu ambiguous, and the deep-link
    /// grammar has to refuse an ambiguous reference rather than guess which one was meant.
    private var duplicateName: Bool {
        !trimmedName.isEmpty && profiles.nameTaken(trimmedName, except: draft.editingID)
    }

    private var isNameAcceptable: Bool { !trimmedName.isEmpty && !duplicateName }

    private var nameFootnote: String {
        if duplicateName { return "Another profile is already called “\(trimmedName)”." }
        return draft.editingID == nil
            ? "A new profile inherits every setting. Change one here and only that one is overridden."
            : "Hosts and pinned cards follow this profile by id, so renaming keeps them attached."
    }

    private func commit() {
        guard isNameAcceptable else { return }
        if let id = draft.editingID {
            profiles.rename(id, to: trimmedName)
            profiles.setAccent(id, to: accent)
        } else {
            var profile = StreamProfile(name: trimmedName, accent: accent)
            profile.overrides = draft.overrides
            profiles.add(profile)
            // Land in the thing that was just made — creating a profile and being left on the
            // defaults is how you end up editing the wrong layer.
            onScope(.profile(profile.id))
        }
        dismiss()
    }
}
