import SwiftUI
import NucleicCore

/// Global quick-capture sheet (Cmd-T). Jot an idea — optionally tag a project —
/// and it lands in the home to-do inbox to dispatch later.
struct QuickTodoSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss
    @State private var text = ""
    @State private var projectID: ProjectID?
    @FocusState private var focused: Bool

    var body: some View {
        VStack(alignment: .leading, spacing: 14) {
            Text("New idea").font(.headline)
            IdeaEditor(text: $text, focused: $focused)
            HStack {
                Menu {
                    Button("No project") { projectID = nil }
                    if !store.projects.isEmpty { Divider() }
                    ForEach(store.projects) { project in
                        Button(project.name) { projectID = project.id }
                    }
                } label: {
                    HStack(spacing: 4) {
                        Image(systemName: "folder")
                        Text(selectedProjectName)
                        Image(systemName: "chevron.down").font(.caption2)
                    }
                }
                .menuStyle(.button)
                .menuIndicator(.hidden)  // we draw our own chevron in the label
                .fixedSize()
                Spacer()
                Text("⌘↵ to add").font(.caption2).foregroundStyle(.tertiary)
                Button("Cancel", role: .cancel) { dismiss() }
                    .keyboardShortcut(.cancelAction)
                    .tint(nil)  // drop the inherited app accent; dimmed teal is hard to read in dark mode
                Button("Add") { save() }
                    .keyboardShortcut(.return, modifiers: .command)
                    .buttonStyle(.borderedProminent)
                    .disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
            }
            .font(.callout)
        }
        .padding(20)
        .frame(width: 460)
        .onAppear {
            projectID = store.quickTodoProjectID  // pre-select when opened from a project page
            focused = true
        }
    }

    private var selectedProjectName: String {
        guard let projectID, let project = store.project(projectID) else { return "No project" }
        return project.name
    }

    private func save() {
        let value = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !value.isEmpty else { return }
        let pid = projectID
        Task { await store.addTodo(value, projectID: pid) }
        dismiss()
    }
}

/// Edit a captured idea's text and project (from the home or project page). The
/// text is what gets dispatched as the prompt, so it edits the original — the
/// summary regenerates from the new text.
struct EditTodoSheet: View {
    @Environment(AppStore.self) private var store
    @Environment(\.dismiss) private var dismiss
    let todoID: TodoID
    @State private var text = ""
    @State private var projectID: ProjectID?
    @FocusState private var focused: Bool

    var body: some View {
        VStack(alignment: .leading, spacing: 14) {
            Text("Edit idea").font(.headline)
            IdeaEditor(text: $text, focused: $focused)
            HStack {
                Menu {
                    Button("No project") { projectID = nil }
                    if !store.projects.isEmpty { Divider() }
                    ForEach(store.projects) { project in
                        Button(project.name) { projectID = project.id }
                    }
                } label: {
                    HStack(spacing: 4) {
                        Image(systemName: "folder")
                        Text(selectedProjectName)
                        Image(systemName: "chevron.down").font(.caption2)
                    }
                }
                .menuStyle(.button)
                .menuIndicator(.hidden)  // we draw our own chevron in the label
                .fixedSize()
                Spacer()
                Text("⌘↵ to save").font(.caption2).foregroundStyle(.tertiary)
                Button("Cancel", role: .cancel) { dismiss() }
                    .keyboardShortcut(.cancelAction)
                    .tint(nil)  // drop the inherited app accent; dimmed teal is hard to read in dark mode
                Button("Save") { save() }
                    .keyboardShortcut(.return, modifiers: .command)
                    .buttonStyle(.borderedProminent)
                    .disabled(text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty)
            }
            .font(.callout)
        }
        .padding(20)
        .frame(width: 460)
        .onAppear {
            if let todo = store.todo(todoID) { text = todo.text; projectID = todo.projectID }
            focused = true
        }
    }

    private var selectedProjectName: String {
        guard let projectID, let project = store.project(projectID) else { return "No project" }
        return project.name
    }

    private func save() {
        let value = text.trimmingCharacters(in: .whitespacesAndNewlines)
        guard !value.isEmpty else { return }
        let id = todoID
        let pid = projectID
        Task { await store.updateTodo(id, text: value, projectID: pid) }
        dismiss()
    }
}

/// A roomy multi-line composer for fleshing out an idea — a scrolling TextEditor
/// (with a placeholder overlay, which TextEditor lacks natively) so a thought can
/// grow into a few paragraphs instead of a cramped one-liner. The whole thing is
/// the dispatched prompt, so there's room to explore before sending it off.
private struct IdeaEditor: View {
    @Binding var text: String
    var placeholder = "What's on your mind? Jot the gist, then flesh it out…"
    @FocusState.Binding var focused: Bool

    var body: some View {
        ZStack(alignment: .topLeading) {
            // Native TextEditor has no placeholder, so float one behind the cursor
            // until the user types. Insets match TextEditor's internal text container.
            if text.isEmpty {
                Text(placeholder)
                    .foregroundStyle(.tertiary)
                    .padding(.horizontal, 5)
                    .padding(.vertical, 8)
                    .allowsHitTesting(false)
            }
            TextEditor(text: $text)
                .font(.body)
                .scrollContentBackground(.hidden)
                .focused($focused)
                .frame(minHeight: 160, maxHeight: 360)
        }
        .padding(6)
        .background(.quaternary.opacity(0.5), in: .rect(cornerRadius: 8))
    }
}

/// The home dashboard's to-do inbox: open ideas grouped by project, each group
/// with a one-line summary, and each idea dispatchable as an agent run when the
/// user chooses (pick the project from the Dispatch menu).
struct TodoSection: View {
    @Environment(AppStore.self) private var store
    @Environment(\.appPalette) private var palette

    var body: some View {
        let groups = store.todoGroups
        VStack(alignment: .leading, spacing: 12) {
            HStack {
                Text("To-Do").font(.headline)
                Spacer()
                if !groups.isEmpty { TriageButton() }
                Button {
                    store.presentQuickTodo()
                } label: {
                    Label("Add idea", systemImage: "plus")
                }
                .buttonStyle(.borderless)
                .font(.callout)
                .help("Capture a new idea (⌘T)")
            }
            if groups.isEmpty {
                Text("No ideas yet — press ⌘T to jot one down.")
                    .font(.callout).foregroundStyle(.secondary)
            } else {
                // Lazy so the home page doesn't build every group's rows (each carrying
                // NSMenu-backed controls) up front — the render cost no longer scales with
                // the total to-do count.
                LazyVStack(alignment: .leading, spacing: 16) {
                    ForEach(groups) { group in
                        TodoGroupView(group: group)
                    }
                }
            }
        }
    }
}

/// The to-do inbox scoped to a single project, for the project overview page:
/// just that project's open ideas (with the group summary), and an add button
/// that pre-tags the project.
struct ProjectTodoSection: View {
    @Environment(AppStore.self) private var store
    let projectID: ProjectID

    private var group: TodoGroup? {
        store.todoGroups.first { $0.projectID == projectID }
    }

    var body: some View {
        let todos = group?.todos ?? []
        VStack(alignment: .leading, spacing: 12) {
            HStack(spacing: 6) {
                Text("To-Do").font(.headline)
                if !todos.isEmpty {
                    Text("\(todos.count)")
                        .font(.caption2.monospacedDigit())
                        .padding(.horizontal, 6).padding(.vertical, 1)
                        .background(.quaternary.opacity(0.6), in: .capsule)
                        .foregroundStyle(.secondary)
                }
                Spacer()
                if !todos.isEmpty { TriageButton() }
                Button {
                    store.presentQuickTodo(projectID: projectID)
                } label: {
                    Label("Add idea", systemImage: "plus")
                }
                .buttonStyle(.borderless)
                .font(.callout)
                .help("Capture an idea for this project (⌘T)")
            }
            if todos.isEmpty {
                Text("No ideas for this project yet — press ⌘T to jot one down.")
                    .font(.callout).foregroundStyle(.secondary)
            } else {
                if let summary = group?.summary, !summary.isEmpty {
                    Text(summary)
                        .font(.caption).foregroundStyle(.secondary)
                        .fixedSize(horizontal: false, vertical: true)
                }
                LazyVStack(spacing: 0) {
                    ForEach(todos) { todo in
                        TodoRow(todo: todo)
                        if todo.id != todos.last?.id { Divider() }
                    }
                }
                // Slide rows into place when the order changes (triage toggle or a
                // background re-rank), so it's visible they actually reorganized.
                .animation(.spring(response: 0.38, dampingFraction: 0.82), value: todos.map(\.id))
                .background(AppTheme.surface, in: .rect(cornerRadius: 12))
                .overlay(RoundedRectangle(cornerRadius: 12).strokeBorder(AppTheme.hairline, lineWidth: 1))
            }
        }
    }
}

private struct TodoGroupView: View {
    @Environment(AppStore.self) private var store
    @Environment(\.appPalette) private var palette
    let group: TodoGroup
    @State private var dropTargeted = false

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            HStack(spacing: 6) {
                Image(systemName: group.projectID == nil ? "tray" : "folder")
                    .font(.caption).foregroundStyle(.secondary)
                Text(group.projectName).font(.subheadline.weight(.semibold))
                Text("\(group.todos.count)")
                    .font(.caption2.monospacedDigit())
                    .padding(.horizontal, 6).padding(.vertical, 1)
                    .background(.quaternary.opacity(0.6), in: .capsule)
                    .foregroundStyle(.secondary)
            }
            if !group.summary.isEmpty {
                Text(group.summary)
                    .font(.caption)
                    .foregroundStyle(.secondary)
                    .fixedSize(horizontal: false, vertical: true)
            }
            LazyVStack(spacing: 0) {
                ForEach(group.todos) { todo in
                    TodoRow(todo: todo)
                    if todo.id != group.todos.last?.id { Divider() }
                }
            }
            // Slide rows into place when the order changes (triage toggle or a
            // background re-rank), so it's visible they actually reorganized.
            .animation(.spring(response: 0.38, dampingFraction: 0.82), value: group.todos.map(\.id))
            .background(AppTheme.surface, in: .rect(cornerRadius: 12))
            .overlay(RoundedRectangle(cornerRadius: 12)
                .strokeBorder(dropTargeted ? palette.accent : AppTheme.hairline,
                              lineWidth: dropTargeted ? 2 : 1))
        }
        // Drop a dragged todo here to reassign it to this group's project.
        .dropDestination(for: String.self) { ids, _ in
            guard !ids.isEmpty else { return false }
            let target = group.projectID
            for raw in ids {
                Task { await store.moveTodo(TodoID(rawValue: raw), toProject: target) }
            }
            return true
        } isTargeted: { dropTargeted = $0 }
    }
}

private struct TodoRow: View {
    @Environment(AppStore.self) private var store
    @Environment(\.appPalette) private var palette
    let todo: Todo

    private var taggedProject: Project? {
        todo.projectID.flatMap { store.project($0) }
    }

    /// A model/heuristic summary distinct from the raw text (nil/"" → none).
    private var summary: String? {
        guard let summary = todo.summary, !summary.isEmpty else { return nil }
        return summary
    }

    var body: some View {
        HStack(spacing: 10) {
            // Leading content is the drag handle — drop a todo onto another project
            // group (home screen) to reassign it. The trailing buttons stay tappable.
            HStack(spacing: 10) {
                // The triage badge replaces the lightbulb once the inbox is triaged, so
                // the impact level reads at a glance down the left edge. When triaged the
                // badge sits in a fixed-width column (see TriageLabelCell) so every row's
                // title and subtext line up like a table, regardless of label length.
                if store.triageSortEnabled, let item = store.triageItem(for: todo.id) {
                    TriageLabelCell(item: item)
                } else {
                    Image(systemName: "lightbulb")
                        .foregroundStyle(.secondary)
                }
                VStack(alignment: .leading, spacing: 2) {
                    if let summary {
                        HStack(spacing: 4) {
                            Image(systemName: "sparkles")
                                .font(.caption2)
                                .foregroundStyle(palette.accent)
                                .help("Summarized — the full text below is used as the prompt")
                            Text(summary).lineLimit(2)
                        }
                        // The original prompt text: smaller and dimmer, but still shown
                        // since it's what gets dispatched as the agent's prompt.
                        Text(todo.text)
                            .font(.caption)
                            .foregroundStyle(.tertiary)
                            .lineLimit(2)
                    } else {
                        Text(todo.text).lineLimit(2)
                    }
                }
            }
            .contentShape(Rectangle())
            .draggable(todo.id.rawValue)
            Spacer()
            dispatchControl
            overflowMenu
        }
        .padding(12)
    }

    /// Secondary actions folded into a single "…" menu so the row stays uncluttered
    /// with only Dispatch prominent: edit, move to another project, mark done, delete.
    private var overflowMenu: some View {
        Menu {
            Button {
                store.editingTodoID = todo.id
            } label: {
                Label("Edit…", systemImage: "pencil")
            }
            Menu {
                Button {
                    Task { await store.moveTodo(todo.id, toProject: nil) }
                } label: {
                    Label("Unassigned", systemImage: todo.projectID == nil ? "checkmark" : "tray")
                }
                if !store.projects.isEmpty { Divider() }
                ForEach(store.projects) { project in
                    Button {
                        Task { await store.moveTodo(todo.id, toProject: project.id) }
                    } label: {
                        Label(project.name, systemImage: todo.projectID == project.id ? "checkmark" : "folder")
                    }
                }
            } label: {
                Label("Move to", systemImage: "folder")
            }
            Button {
                Task { await store.setTodoStatus(todo.id, .done) }
            } label: {
                Label("Mark done", systemImage: "checkmark.circle")
            }
            Divider()
            Button(role: .destructive) {
                Task { await store.deleteTodo(todo.id) }
            } label: {
                Label("Delete", systemImage: "trash")
            }
        } label: {
            Image(systemName: "ellipsis")
        }
        .menuStyle(.borderlessButton)
        .menuIndicator(.hidden)
        .fixedSize()
        .help("More actions")
    }

    /// "Dispatch" fires an agent on the idea. If a project is tagged, one click runs
    /// it there; otherwise the user picks a project from the menu.
    @ViewBuilder
    private var dispatchControl: some View {
        if let project = taggedProject {
            // A plain button that fires the agent in the tagged project and stays put.
            Button {
                Task { await store.dispatchTodo(todo.id, in: project, follow: false) }
            } label: {
                Label("Dispatch", systemImage: "paperplane.fill")
            }
            .buttonStyle(.borderedProminent)
            // Pin to gray: .borderedProminent otherwise inherits the teal accent. It read
            // gray for a while because the old .menuStyle(.button) override suppressed the
            // prominent tint; that override went away when this stopped being a menu, so the
            // teal crept back. Tinting gray restores the intended look.
            .tint(.gray)
            .fixedSize()
            .help("Start an agent on this in \(project.name)")
        } else if store.projects.isEmpty {
            Button {} label: { Label("Dispatch", systemImage: "paperplane.fill") }
                .buttonStyle(.bordered)
                .disabled(true)
                .help("Add a project first")
        } else {
            Menu {
                ForEach(store.projects) { project in
                    Button(project.name) {
                        Task { await store.dispatchTodo(todo.id, in: project) }
                    }
                }
            } label: {
                // Custom chevron with the built-in indicator hidden, so the disclosure sits
                // inside the button's trailing inset with breathing room instead of jammed
                // against the right edge the way the system indicator renders.
                HStack(spacing: 6) {
                    Label("Dispatch", systemImage: "paperplane.fill")
                    Image(systemName: "chevron.down").font(.caption2)
                }
            }
            .menuStyle(.button)
            .menuIndicator(.hidden)
            .fixedSize()
            .help("Pick a project to start an agent on this idea")
        }
    }
}

/// Toggles the inbox between FIFO and the AI impact ranking. Reads as a state, not an
/// action, so the label names the *current* state: "Triage" (off) vs "Triaged"
/// (on, accent-tinted with a check). A spinner rides alongside while a background pass
/// refreshes the stored verdicts.
private struct TriageButton: View {
    @Environment(AppStore.self) private var store
    @Environment(\.appPalette) private var palette

    var body: some View {
        Button {
            store.toggleTriageSort()
        } label: {
            // Baseline-align (like Label does) so the icon sits level with the text
            // instead of riding low on the row's vertical center.
            HStack(alignment: .firstTextBaseline, spacing: 5) {
                if store.triaging {
                    ProgressView().controlSize(.small)
                        // The spinner has no text baseline; pin its center to the baseline
                        // so it stays visually centered next to the label.
                        .alignmentGuide(.firstTextBaseline) { $0[VerticalAlignment.center] }
                }
                // Only the icon animates the state flip — it morphs (replace) while the
                // text and tint switch instantly. Scoping the animation to the Image
                // keeps the text from crossfading.
                Image(systemName: store.triageSortEnabled ? "checkmark.circle.fill" : "wand.and.stars")
                    .contentTransition(.symbolEffect(.replace))
                    .animation(.smooth(duration: 0.09), value: store.triageSortEnabled)
                Text(store.triageSortEnabled ? "Triaged" : "Triage")
            }
        }
        .buttonStyle(.borderless)
        .font(.callout)
        .tint(store.triageSortEnabled ? palette.accent : nil)
        .help(store.triageSortEnabled
              ? "Ideas are in impact order — click to go back to oldest-first"
              : "Sort ideas by impact (AI-ranked as you add them)")
    }
}

/// The triage badge sized into a fixed-width column so every triaged row's title and
/// subtext start at the same x — the inbox reads like a table. The column width tracks
/// the widest level label automatically (hidden sizer badges, one per level), so it
/// stays correct if the labels change. The visible badge is leading-aligned and snug,
/// keeping the colored dots aligned down the left edge.
private struct TriageLabelCell: View {
    let item: TriageItem

    var body: some View {
        ZStack(alignment: .leading) {
            ForEach(TriageLevel.allCases, id: \.self) { level in
                TriageBadge(item: TriageItem(id: item.id, level: level, rank: item.rank))
                    .hidden()
            }
            TriageBadge(item: item)
        }
    }
}

/// A small colored badge showing an idea's triage level. The dot carries the hue (so
/// any level reads clearly, even on light backgrounds); the reason shows on hover.
private struct TriageBadge: View {
    let item: TriageItem
    // Encouraging vs classic severity wording (Settings → Appearance). The hidden
    // sizers in TriageLabelCell read the same key, so the column width tracks whichever
    // labels are active.
    @AppStorage(TriageLabelStyle.encouragingKey) private var encouraging = true

    private var label: String { item.level.label(encouraging: encouraging) }
    private var tint: Color { item.level.tint(encouraging: encouraging) }

    var body: some View {
        HStack(spacing: 4) {
            Circle().fill(tint).frame(width: 7, height: 7)
            Text(label)
                .font(.caption2.weight(.semibold))
                .foregroundStyle(.secondary)
        }
        .padding(.horizontal, 7).padding(.vertical, 3)
        .background(tint.opacity(0.14), in: .capsule)
        .overlay(Capsule().strokeBorder(tint.opacity(0.35), lineWidth: 0.5))
        .fixedSize()
        .help(item.reason.map { "\(label): \($0)" } ?? "Impact: \(label)")
    }
}

extension TriageLevel {
    /// The badge hue per level, matched to the labeling mode:
    ///
    /// - Classic (`encouraging == false`): a red → orange → yellow → gray urgency
    ///   scale, matching the Critical/High/Medium/Low severity words.
    /// - Encouraging (`encouraging == true`): a reward scale across four distinct hue
    ///   families — slate → green → blue → magenta — with saturation climbing toward
    ///   the top, so the tiers stay easy to tell apart (even at the 7pt dot) and the
    ///   top tier (Max Impact) pops as a prize rather than an emergency.
    func tint(encouraging: Bool) -> Color {
        if !encouraging {
            switch self {
            case .critical: return .red
            case .high: return .orange
            case .medium: return .yellow
            case .low: return .gray
            }
        }
        switch self {
        case .critical: return Color(red: 0.95, green: 0.33, blue: 0.78)  // vivid magenta — the standout
        case .high: return Color(red: 0.26, green: 0.54, blue: 1.00)       // bright blue
        case .medium: return Color(red: 0.18, green: 0.78, blue: 0.45)     // green
        case .low: return Color(red: 0.56, green: 0.60, blue: 0.67)        // calm slate
        }
    }
}
