import SwiftUI
import SharedModels
import SwiftData
import PencilKit

/// Interactive fill-in-the-blank view for one textbook exercise.
/// Supports keyboard typing OR Apple Pencil handwriting input per prompt.
struct TextbookExerciseView: View {
    let chapter: TextbookChapter
    let blockIndex: Int

    @Environment(\.cloudModelContextProvider) private var cloudModelContextProvider
    @State private var answers: [Int: String] = [:]
    @State private var drawings: [Int: PKDrawing] = [:]
    @State private var grades: [Int: TextbookGrade] = [:]
    @State private var inputMode: InputMode = .keyboard
    @State private var activePencilPromptNumber: Int?
    @State private var isRecognizing = false
    @State private var isChecked = false
    @State private var recognizedTextForActive: String = ""

    private var cloudModelContext: ModelContext { cloudModelContextProvider() }

    enum InputMode: String {
        case keyboard
        case pencil
    }

    private var block: TextbookBlock? {
        chapter.blocks().first { $0.index == blockIndex }
    }

    private var answerByNumber: [Int: TextbookAnswerItem] {
        guard let items = block?.answerItems else { return [:] }
        var out: [Int: TextbookAnswerItem] = [:]
        for it in items {
            out[it.number] = it
        }
        return out
    }

    var body: some View {
        ScrollView {
            VStack(alignment: .leading, spacing: 16) {
                if let b = block {
                    headerView(b)
                    inputModePicker
                    exerciseBody(b)
                    checkButton(b)
                } else {
                    ContentUnavailableView(
                        "Exercise not found",
                        systemImage: "questionmark.circle"
                    )
                }
            }
            .padding()
        }
        .navigationTitle("Exercise \(block?.exerciseId ?? "")")
        .navigationBarTitleDisplayMode(.inline)
        .onAppear(perform: loadPreviousAttempt)
    }

    private func headerView(_ b: TextbookBlock) -> some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("Chapter \(chapter.number): \(chapter.title)")
                .font(.caption)
                .foregroundStyle(.secondary)
            Text("Exercise \(b.exerciseId ?? "")")
                .font(.title2.bold())
            if let inst = b.instruction, !inst.isEmpty {
                Text(stripInlineEmphasis(inst))
                    .font(.callout)
                    .foregroundStyle(.secondary)
                    .fixedSize(horizontal: false, vertical: true)
            }
            if let extra = b.extra, !extra.isEmpty {
                ForEach(Array(extra.enumerated()), id: \.offset) { _, e in
                    Text(stripInlineEmphasis(e))
                        .font(.callout)
                        .fixedSize(horizontal: false, vertical: true)
                        .padding(8)
                        .frame(maxWidth: .infinity, alignment: .leading)
                        .background(Color.secondary.opacity(0.1), in: RoundedRectangle(cornerRadius: 8))
                }
            }
        }
    }

    private var inputModePicker: some View {
        Picker("Input mode", selection: $inputMode) {
            Label("Keyboard", systemImage: "keyboard").tag(InputMode.keyboard)
            Label("Pencil", systemImage: "pencil.tip").tag(InputMode.pencil)
        }
        .pickerStyle(.segmented)
    }

    private func exerciseBody(_ b: TextbookBlock) -> some View {
        VStack(alignment: .leading, spacing: 14) {
            if b.freeform == true {
                VStack(alignment: .leading, spacing: 6) {
                    Label("Freeform exercise", systemImage: "text.bubble")
                        .font(.subheadline.weight(.semibold))
                        .foregroundStyle(.orange)
                    Text("Answers will vary. Use this space to write your own responses; they won't be auto-checked.")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                .padding()
                .background(Color.orange.opacity(0.1), in: RoundedRectangle(cornerRadius: 10))
            }
            let rawPrompts = b.prompts ?? []
            let prompts = rawPrompts.isEmpty ? synthesizedPrompts(b) : rawPrompts
            if prompts.isEmpty && b.extra?.isEmpty == false {
                Text("Fill in the blanks above; answers will be graded when you tap Check.")
                    .font(.caption)
                    .foregroundStyle(.secondary)
            } else {
                ForEach(Array(prompts.enumerated()), id: \.offset) { i, prompt in
                    promptRow(index: i, prompt: prompt, expected: answerByNumber[i + 1])
                }
            }
        }
    }

    /// When the source exercise prompts were embedded in a bitmap (common in
    /// this textbook), we have no text for each question — only the answer
    /// key. Synthesize numbered placeholders so the user still gets one input
    /// field per answer.
    private func synthesizedPrompts(_ b: TextbookBlock) -> [String] {
        guard let items = b.answerItems, !items.isEmpty else { return [] }
        return items.map { "\($0.number)." }
    }

    private func promptRow(index: Int, prompt: String, expected: TextbookAnswerItem?) -> some View {
        let number = index + 1
        let grade = grades[number]
        return VStack(alignment: .leading, spacing: 8) {
            HStack(alignment: .top, spacing: 8) {
                if let grade {
                    Image(systemName: iconFor(grade))
                        .foregroundStyle(colorFor(grade))
                        .font(.title3)
                        .padding(.top, 2)
                }
                Text(stripInlineEmphasis(prompt))
                    .font(.body)
                    .fixedSize(horizontal: false, vertical: true)
            }

            switch inputMode {
            case .keyboard:
                TextField("Your answer", text: binding(for: number))
                    .textFieldStyle(.roundedBorder)
                    .textInputAutocapitalization(.never)
                    .disableAutocorrection(true)
                    .font(.body)
                    .disabled(isChecked)
            case .pencil:
                pencilRow(number: number)
            }

            if isChecked, let grade, grade != .correct, let expected {
                HStack(spacing: 6) {
                    Text("Answer:")
                        .font(.caption.weight(.semibold))
                    Text(expected.answer)
                        .font(.caption)
                    if !expected.alternates.isEmpty {
                        Text("(also: \(expected.alternates.joined(separator: ", ")))")
                            .font(.caption2)
                            .foregroundStyle(.secondary)
                    }
                }
                .foregroundStyle(colorFor(grade))
            }
        }
        .padding(10)
        .background(backgroundFor(grade), in: RoundedRectangle(cornerRadius: 8))
    }

    private func pencilRow(number: Int) -> some View {
        VStack(alignment: .leading, spacing: 6) {
            HandwritingCanvas(
                drawing: bindingDrawing(for: number),
                onDrawingChanged: { recognizePencil(for: number) }
            )
            .frame(height: 100)
            .background(.fill.quinary, in: RoundedRectangle(cornerRadius: 10))
            .overlay(RoundedRectangle(cornerRadius: 10).stroke(.separator, lineWidth: 1))

            HStack {
                if let typed = answers[number], !typed.isEmpty {
                    Text("Recognized: \(typed)")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                }
                Spacer()
                Button("Clear") {
                    drawings[number] = PKDrawing()
                    answers[number] = ""
                }
                .font(.caption)
                .tint(.secondary)
            }
        }
    }

    private func checkButton(_ b: TextbookBlock) -> some View {
        let hasAnyAnswer = answers.values.contains { !$0.isEmpty }
        let disabled = b.freeform == true || (!isChecked && !hasAnyAnswer)
        return Button {
            if isChecked {
                resetExercise()
            } else {
                checkAnswers(b)
            }
        } label: {
            Text(isChecked ? "Try again" : "Check answers")
                .font(.headline)
                .frame(maxWidth: .infinity)
                .padding(.vertical, 10)
        }
        .buttonStyle(.borderedProminent)
        .tint(.orange)
        .disabled(disabled)
    }

    // MARK: - Actions

    private func checkAnswers(_ b: TextbookBlock) {
        guard let prompts = b.prompts else { return }
        var newGrades: [Int: TextbookGrade] = [:]
        var states: [TextbookPromptState] = []
        for (i, _) in prompts.enumerated() {
            let number = i + 1
            let user = answers[number] ?? ""
            let expected = answerByNumber[number]
            let canonical = expected?.answer ?? ""
            let alts = expected?.alternates ?? []
            let grade: TextbookGrade
            if canonical.isEmpty {
                grade = .wrong
            } else {
                grade = AnswerChecker.grade(userText: user, canonical: canonical, alternates: alts)
            }
            newGrades[number] = grade
            states.append(TextbookPromptState(number: number, userText: user, grade: grade))
        }
        grades = newGrades
        isChecked = true
        ReviewStore.recordActivity(context: cloudModelContext)
        saveAttempt(states: states, exerciseId: b.exerciseId ?? "")
    }

    private func resetExercise() {
        answers.removeAll()
        drawings.removeAll()
        grades.removeAll()
        isChecked = false
    }

    private func recognizePencil(for number: Int) {
        guard let drawing = drawings[number], !drawing.strokes.isEmpty else { return }
        isRecognizing = true
        Task {
            let result = await HandwritingRecognizer.recognize(drawing: drawing)
            await MainActor.run {
                answers[number] = result.text
                isRecognizing = false
            }
        }
    }

    private func saveAttempt(states: [TextbookPromptState], exerciseId: String) {
        let attemptId = TextbookExerciseAttempt.attemptId(
            courseName: chapter.courseName,
            exerciseId: exerciseId
        )
        let descriptor = FetchDescriptor<TextbookExerciseAttempt>(
            predicate: #Predicate<TextbookExerciseAttempt> { $0.id == attemptId }
        )
        let context = cloudModelContext
        let existing = (try? context.fetch(descriptor))?.first
        let attempt = existing ?? TextbookExerciseAttempt(
            id: attemptId,
            courseName: chapter.courseName,
            chapterNumber: chapter.number,
            exerciseId: exerciseId
        )
        if existing == nil { context.insert(attempt) }
        attempt.lastAttemptAt = Date()
        attempt.setPromptStates(states)
        try? context.save()
    }

    private func loadPreviousAttempt() {
        guard let b = block else { return }
        let attemptId = TextbookExerciseAttempt.attemptId(
            courseName: chapter.courseName,
            exerciseId: b.exerciseId ?? ""
        )
        let descriptor = FetchDescriptor<TextbookExerciseAttempt>(
            predicate: #Predicate<TextbookExerciseAttempt> { $0.id == attemptId }
        )
        guard let attempt = (try? cloudModelContext.fetch(descriptor))?.first else { return }
        for s in attempt.promptStates() {
            answers[s.number] = s.userText
            grades[s.number] = s.grade
        }
        isChecked = !grades.isEmpty
    }

    // MARK: - Bindings

    private func binding(for number: Int) -> Binding<String> {
        Binding(
            get: { answers[number] ?? "" },
            set: { answers[number] = $0 }
        )
    }

    private func bindingDrawing(for number: Int) -> Binding<PKDrawing> {
        Binding(
            get: { drawings[number] ?? PKDrawing() },
            set: { drawings[number] = $0 }
        )
    }

    // MARK: - UI helpers

    private func iconFor(_ grade: TextbookGrade) -> String {
        switch grade {
        case .correct: return "checkmark.circle.fill"
        case .close: return "circle.lefthalf.filled"
        case .wrong: return "xmark.circle.fill"
        }
    }

    private func colorFor(_ grade: TextbookGrade) -> Color {
        switch grade {
        case .correct: return .green
        case .close: return .orange
        case .wrong: return .red
        }
    }

    private func backgroundFor(_ grade: TextbookGrade?) -> Color {
        guard let grade else { return Color.secondary.opacity(0.05) }
        switch grade {
        case .correct: return .green.opacity(0.12)
        case .close: return .orange.opacity(0.12)
        case .wrong: return .red.opacity(0.12)
        }
    }

    private func stripInlineEmphasis(_ s: String) -> String {
        s.replacingOccurrences(of: "**", with: "")
            .replacingOccurrences(of: "*", with: "")
    }
}
