import SwiftUI
import SwiftData

/// Which sidebar item is selected. `routine` carries the routine's ULID; the two
/// Library items are singletons. Starter Gallery and New Routine are actions
/// (sheets), not selectable destinations, so they're deliberately absent here.
enum SidebarSelection: Hashable {
    case routine(String)   // ULID
    case schedules
    case exercises
}

/// A routine flagged for deletion — captured as plain values (id + name), not a
/// retained `@Model`. Reading a persisted property on a since-deleted entity traps
/// (the repo's known crash class), so the confirmation dialog reads only these
/// captured strings and re-resolves the live routine by id at delete time.
private struct PendingRoutineDelete: Identifiable {
    let id: String
    let name: String
}

/// A routine flagged for editing — captured as just its id, not a retained `@Model`
/// (see `PendingRoutineDelete`). The sheet re-resolves the live routine by id when it
/// presents, so a remote delete between the tap and the sheet's next body evaluation
/// can't hand the editor a dangling entity.
private struct PendingRoutineEdit: Identifiable {
    let id: String
}

/// Root content once iCloud is available: a Mac-native `NavigationSplitView` shell.
/// The sidebar lists the two Library destinations plus every routine (reorderable,
/// with a per-row context menu); the detail column swaps on selection. New Routine
/// and the Starter Gallery are surfaced as sheets from the window toolbar.
struct MacContentView: View {
    @Environment(SyncEngine.self) private var syncEngine

    @Query(sort: [SortDescriptor(\Routine.order), SortDescriptor(\Routine.name)])
    private var routines: [Routine]
    @Query private var schedules: [Schedule]

    @State private var selection: SidebarSelection?
    @State private var showingNewRoutine = false
    @State private var showingStarterGallery = false
    @State private var routineToEdit: PendingRoutineEdit?
    @State private var pendingDelete: PendingRoutineDelete?

    var body: some View {
        NavigationSplitView {
            sidebar
        } detail: {
            detail
        }
        // Attached to the split view, not the sidebar List: sidebar-column toolbar
        // items are confined to the sidebar's (narrow) section of the unified
        // toolbar and collapse into the overflow menu. Here they get the full
        // trailing section instead.
        .toolbar {
            ToolbarItemGroup(placement: .primaryAction) {
                Button {
                    showingNewRoutine = true
                } label: {
                    Label("New Routine", systemImage: "plus")
                }
                .help("New Routine")

                Button {
                    showingStarterGallery = true
                } label: {
                    Label("Starter Gallery", systemImage: "sparkles")
                }
                .help("Browse Starter Routines")
            }
        }
        .sheet(isPresented: $showingNewRoutine) {
            MacRoutineEditorSheet(routine: nil)
        }
        .sheet(isPresented: $showingStarterGallery) {
            MacStarterGalleryView()
        }
        .sheet(item: $routineToEdit) { pending in
            if let routine = routines.first(where: { $0.id == pending.id }), routine.isLive {
                MacRoutineEditorSheet(routine: routine)
            }
        }
        .confirmationDialog(
            "Delete Routine?",
            isPresented: Binding(
                get: { pendingDelete != nil },
                set: { if !$0 { pendingDelete = nil } }
            ),
            titleVisibility: .visible,
            presenting: pendingDelete
        ) { pending in
            Button("Delete", role: .destructive) { confirmDelete(pending) }
            Button("Cancel", role: .cancel) { pendingDelete = nil }
        } message: { pending in
            Text(deleteMessage(for: pending))
        }
        // Follow a seed fork: a clone-on-edit swaps the selected routine's id, so
        // keep the sidebar highlight pointed at the live clone. The detail column
        // follows on its own (it re-resolves the id every render).
        .onChange(of: syncEngine.cloneRedirects) {
            guard case let .routine(id) = selection else { return }
            let resolved = syncEngine.currentRoutineID(for: id)
            if resolved != id { selection = .routine(resolved) }
        }
    }

    // MARK: - Sidebar

    private var sidebar: some View {
        List(selection: $selection) {
            Section("Library") {
                Label("Schedules", systemImage: "calendar")
                    .tag(SidebarSelection.schedules)
                Label("Exercises", systemImage: "figure.strengthtraining.traditional")
                    .tag(SidebarSelection.exercises)
            }

            Section("Routines") {
                ForEach(routines) { routine in
                    RoutineSidebarRow(routine: routine)
                        .tag(SidebarSelection.routine(routine.id))
                        .contextMenu {
                            Button("Edit…") { routineToEdit = PendingRoutineEdit(id: routine.id) }
                            Button("Duplicate") {
                                Task { await syncEngine.duplicate(routine: routine) }
                            }
                            Divider()
                            Button("Delete", role: .destructive) {
                                pendingDelete = PendingRoutineDelete(id: routine.id, name: routine.name)
                            }
                        }
                }
                .onMove(perform: moveRoutines)

                if routines.isEmpty {
                    Text("No routines yet.")
                        .foregroundStyle(.secondary)
                        .font(.callout)
                }
            }
        }
        .navigationTitle("Workouts")
    }

    // MARK: - Detail

    @ViewBuilder
    private var detail: some View {
        switch selection {
        case .routine(let id):
            // Reading `currentRoutineID` here establishes the observation dependency
            // on `cloneRedirects`, so a fork re-renders this branch and the `.id`
            // reseeds the detail's own state onto the clone.
            let resolved = syncEngine.currentRoutineID(for: id)
            MacRoutineDetailView(routineID: resolved)
                .id(resolved)
        case .schedules:
            MacSchedulesView()
        case .exercises:
            MacExercisesBrowserView()
        case nil:
            ContentUnavailableView(
                "Your Library",
                systemImage: "dumbbell",
                description: Text("Select a routine, or pick Schedules or Exercises from the sidebar.")
            )
        }
    }

    // MARK: - Actions

    /// Reorder and renumber via `RoutineOrdering`, writing only the routines whose
    /// order actually changed. Order-only writes deliberately leave `updatedAt`
    /// alone — touching it would fork a starter (its `order` is normalized out of the
    /// pristine check, but `updatedAt` is stamped on every real edit). Mirrors iOS
    /// `RoutinesLibraryView.moveRoutines`.
    private func moveRoutines(from source: IndexSet, to destination: Int) {
        let ids = routines.map(\.id)
        let currentOrders = Dictionary(uniqueKeysWithValues: routines.map { ($0.id, $0.order) })
        let changes = RoutineOrdering.changedOrders(
            ids: ids, currentOrders: currentOrders, from: source, to: destination)
        guard !changes.isEmpty else { return }

        Task {
            for change in changes {
                guard let routine = routines.first(where: { $0.id == change.id }), routine.isLive else { continue }
                var doc = RoutineDocument(from: routine)
                doc.order = change.order
                await syncEngine.save(routine: doc)
            }
        }
    }

    private func confirmDelete(_ pending: PendingRoutineDelete) {
        if let routine = routines.first(where: { $0.id == pending.id }), routine.isLive {
            Task { await syncEngine.delete(routine: routine) }
        }
        if case .routine(pending.id) = selection { selection = nil }
        pendingDelete = nil
    }

    /// Delete-confirmation body, extended with a note about any schedules that point
    /// at this routine — deleting the routine leaves them behind. Mirrors iOS.
    private func deleteMessage(for pending: PendingRoutineDelete) -> String {
        let base = "This will permanently delete \"\(pending.name)\" and all its exercises. Past workouts are kept."
        let count = schedules.filter { syncEngine.currentRoutineID(for: $0.routineID) == pending.id }.count
        guard count > 0 else { return base }
        return base + " Its \(count) scheduled day\(count == 1 ? "" : "s") will stay on the schedule without a startable routine."
    }
}

/// A single routine row in the sidebar: a rounded-square color badge with the
/// routine's symbol, its name (with a "Starter" capsule for a bundled seed), and an
/// exercise-count caption. Mirrors iOS `RoutineRow`, sans the last-trained line
/// (Mac doesn't run workouts).
private struct RoutineSidebarRow: View {
    let routine: Routine

    var body: some View {
        HStack(spacing: 10) {
            Image(systemName: routine.systemImage)
                .font(.body)
                .foregroundStyle(routineColor)
                .frame(width: 28, height: 28)
                .background(routineColor.opacity(0.12), in: RoundedRectangle(cornerRadius: 6))

            VStack(alignment: .leading, spacing: 1) {
                HStack(spacing: 6) {
                    Text(routine.name)
                    if SeedLibrary.isSeed(id: routine.id) {
                        Text("Starter")
                            .font(.caption2)
                            .foregroundStyle(.secondary)
                            .padding(.horizontal, 5)
                            .padding(.vertical, 1)
                            .background(Color.secondary.opacity(0.15), in: Capsule())
                    }
                }
                Text("\(routine.exercisesArray.count) exercises")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            }
        }
        .padding(.vertical, 2)
    }

    private var routineColor: Color { Color.color(from: routine.color) }
}
