import AVFoundation
import SwiftUI
import UIKit

struct ContentView: View {

    @ObservedObject var renderer: DitherRenderer
    @StateObject private var camera = CameraController()
    @Environment(\.scenePhase) private var scenePhase

    @State private var showSettings = false
    @State private var flashOpacity: Double = 0
    @State private var toastMessage: String?
    @State private var toastToken = 0

    var body: some View {
        ZStack {
            Color.black
                .ignoresSafeArea()

            MetalPreview(renderer: renderer)
                .ignoresSafeArea()

            Color.white
                .ignoresSafeArea()
                .opacity(flashOpacity)
                .allowsHitTesting(false)

            VStack(spacing: 0) {
                topBar
                Spacer(minLength: 0)
                if showSettings {
                    settingsPanel
                        .transition(.move(edge: .bottom).combined(with: .opacity))
                }
                paletteStrip
                shutterRow
            }
            .padding(.horizontal, 16)
            .padding(.bottom, 8)

            statusOverlay

            if let toastMessage {
                toastView(toastMessage)
            }
        }
        .preferredColorScheme(.dark)
        .statusBarHidden(true)
        .onAppear(perform: attach)
        .onChange(of: scenePhase) { _, phase in
            if phase == .active {
                camera.start()
            } else {
                camera.stop()
            }
        }
    }

    private var topBar: some View {
        HStack {
            Text(renderer.palette.name.uppercased())
                .font(.system(size: 13, weight: .semibold, design: .monospaced))
                .foregroundStyle(.white.opacity(0.85))
                .padding(.horizontal, 10)
                .padding(.vertical, 6)
                .background(Capsule().fill(.black.opacity(0.4)))

            Spacer()

            Button {
                withAnimation(.easeInOut(duration: 0.2)) {
                    showSettings.toggle()
                }
            } label: {
                Image(systemName: showSettings ? "slider.horizontal.3" : "slider.horizontal.below.rectangle")
                    .font(.system(size: 18, weight: .medium))
                    .foregroundStyle(.white)
                    .frame(width: 40, height: 40)
                    .background(Circle().fill(.black.opacity(0.4)))
            }
        }
        .padding(.top, 8)
    }

    private var settingsPanel: some View {
        VStack(spacing: 14) {
            matrixPicker

            labeledSlider(
                title: "Pixel Size",
                value: pixelSizeBinding,
                range: 1...10,
                step: 1,
                readout: "\(renderer.pixelSize)"
            )

            labeledSlider(
                title: "Spread",
                value: $renderer.spread,
                range: 0...2,
                step: 0.05,
                readout: String(format: "%.2f", renderer.spread)
            )

            labeledSlider(
                title: "Contrast",
                value: $renderer.contrast,
                range: 0.5...2.5,
                step: 0.05,
                readout: String(format: "%.2f", renderer.contrast)
            )

            labeledSlider(
                title: "Brightness",
                value: $renderer.brightness,
                range: -0.35...0.35,
                step: 0.01,
                readout: String(format: "%+.2f", renderer.brightness)
            )

            Button("Reset") {
                renderer.pixelSize = 2
                renderer.spread = 1.0
                renderer.contrast = 1.15
                renderer.brightness = 0.0
                renderer.matrixSize = 8
            }
            .font(.system(size: 13, weight: .semibold, design: .monospaced))
            .foregroundStyle(.white.opacity(0.7))
        }
        .padding(16)
        .background(
            RoundedRectangle(cornerRadius: 18, style: .continuous)
                .fill(.black.opacity(0.55))
        )
        .padding(.bottom, 12)
    }

    private var matrixPicker: some View {
        HStack(spacing: 10) {
            Text("Matrix")
                .font(.system(size: 12, weight: .semibold, design: .monospaced))
                .foregroundStyle(.white.opacity(0.7))
                .frame(width: 82, alignment: .leading)

            Picker("Matrix", selection: matrixSizeBinding) {
                Text("2x2").tag(2)
                Text("4x4").tag(4)
                Text("8x8").tag(8)
            }
            .pickerStyle(.segmented)
        }
    }

    private func labeledSlider(title: String,
                               value: Binding<Float>,
                               range: ClosedRange<Float>,
                               step: Float,
                               readout: String) -> some View {
        HStack(spacing: 10) {
            Text(title)
                .font(.system(size: 12, weight: .semibold, design: .monospaced))
                .foregroundStyle(.white.opacity(0.7))
                .frame(width: 82, alignment: .leading)

            Slider(value: value, in: range, step: step)
                .tint(.white)

            Text(readout)
                .font(.system(size: 12, weight: .regular, design: .monospaced))
                .foregroundStyle(.white.opacity(0.7))
                .frame(width: 48, alignment: .trailing)
        }
    }

    private var paletteStrip: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 10) {
                ForEach(Palette.all) { palette in
                    Button {
                        renderer.palette = palette
                    } label: {
                        paletteSwatch(palette)
                    }
                    .buttonStyle(.plain)
                }
            }
            .padding(.horizontal, 4)
            .padding(.vertical, 8)
        }
        .frame(height: 62)
    }

    private func paletteSwatch(_ palette: Palette) -> some View {
        let selected = palette.id == renderer.palette.id
        return VStack(spacing: 4) {
            HStack(spacing: 0) {
                ForEach(Array(palette.swiftUIColors.enumerated()), id: \.offset) { item in
                    Rectangle().fill(item.element)
                }
            }
            .frame(width: 58, height: 22)
            .clipShape(RoundedRectangle(cornerRadius: 5, style: .continuous))
            .overlay(
                RoundedRectangle(cornerRadius: 5, style: .continuous)
                    .stroke(selected ? Color.white : Color.white.opacity(0.25), lineWidth: selected ? 2 : 1)
            )

            Text(palette.name)
                .font(.system(size: 9, weight: .medium, design: .monospaced))
                .foregroundStyle(selected ? .white : .white.opacity(0.55))
        }
    }

    private var shutterRow: some View {
        HStack {
            Spacer()
                .frame(width: 56)

            Spacer()

            Button(action: capture) {
                ZStack {
                    Circle()
                        .stroke(Color.white, lineWidth: 4)
                        .frame(width: 74, height: 74)
                    Circle()
                        .fill(Color.white)
                        .frame(width: 60, height: 60)
                }
            }
            .buttonStyle(.plain)
            .disabled(camera.status != .running)
            .opacity(camera.status == .running ? 1 : 0.35)

            Spacer()

            Button {
                camera.flip()
            } label: {
                Image(systemName: "arrow.triangle.2.circlepath.camera")
                    .font(.system(size: 20, weight: .medium))
                    .foregroundStyle(.white)
                    .frame(width: 56, height: 56)
                    .background(Circle().fill(.black.opacity(0.4)))
            }
            .buttonStyle(.plain)
            .disabled(camera.status != .running)
        }
        .padding(.bottom, 10)
    }

    @ViewBuilder
    private var statusOverlay: some View {
        switch camera.status {
        case .unauthorized:
            messageCard(
                title: "Camera Access Needed",
                body: "DitherCam needs the camera to show a live dithered viewfinder.",
                actionTitle: "Open Settings"
            ) {
                if let url = URL(string: UIApplication.openSettingsURLString) {
                    UIApplication.shared.open(url)
                }
            }
        case .failed(let message):
            messageCard(
                title: "Camera Unavailable",
                body: message,
                actionTitle: "Try Again"
            ) {
                camera.start()
            }
        case .idle, .running:
            EmptyView()
        }
    }

    private func messageCard(title: String,
                             body: String,
                             actionTitle: String,
                             action: @escaping () -> Void) -> some View {
        VStack(spacing: 12) {
            Text(title)
                .font(.system(size: 17, weight: .semibold, design: .monospaced))
                .foregroundStyle(.white)

            Text(body)
                .font(.system(size: 13, design: .monospaced))
                .foregroundStyle(.white.opacity(0.75))
                .multilineTextAlignment(.center)

            Button(actionTitle, action: action)
                .font(.system(size: 14, weight: .semibold, design: .monospaced))
                .foregroundStyle(.black)
                .padding(.horizontal, 18)
                .padding(.vertical, 9)
                .background(Capsule().fill(Color.white))
        }
        .padding(24)
        .background(
            RoundedRectangle(cornerRadius: 20, style: .continuous)
                .fill(.black.opacity(0.85))
        )
        .padding(32)
    }

    private func toastView(_ message: String) -> some View {
        VStack {
            Spacer()
            Text(message)
                .font(.system(size: 13, weight: .medium, design: .monospaced))
                .foregroundStyle(.white)
                .padding(.horizontal, 16)
                .padding(.vertical, 10)
                .background(Capsule().fill(.black.opacity(0.75)))
                .padding(.bottom, 190)
        }
        .allowsHitTesting(false)
        .transition(.opacity)
    }

    private var pixelSizeBinding: Binding<Float> {
        Binding(
            get: { Float(renderer.pixelSize) },
            set: { renderer.pixelSize = UInt32(max(1, $0.rounded())) }
        )
    }

    private var matrixSizeBinding: Binding<Int> {
        Binding(
            get: { Int(renderer.matrixSize) },
            set: { renderer.matrixSize = UInt32($0) }
        )
    }

    private func attach() {
        let target = renderer
        camera.setFrameHandler { [weak target] pixelBuffer in
            target?.setPixelBuffer(pixelBuffer)
        }
        renderer.onCapture = { image in
            handleCapture(image)
        }
        camera.start()
    }

    private func capture() {
        UIImpactFeedbackGenerator(style: .medium).impactOccurred()
        renderer.requestCapture()
        withAnimation(.easeOut(duration: 0.06)) {
            flashOpacity = 1
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.07) {
            withAnimation(.easeIn(duration: 0.2)) {
                flashOpacity = 0
            }
        }
    }

    private func handleCapture(_ image: UIImage?) {
        guard let image else {
            showToast("Capture failed")
            return
        }
        PhotoSaver.save(image) { result in
            switch result {
            case .success:
                showToast("Saved to Photos")
            case .failure(let error):
                showToast(error.localizedDescription)
            }
        }
    }

    private func showToast(_ message: String) {
        toastToken += 1
        let token = toastToken
        withAnimation(.easeInOut(duration: 0.18)) {
            toastMessage = message
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.8) {
            guard token == toastToken else { return }
            withAnimation(.easeInOut(duration: 0.25)) {
                toastMessage = nil
            }
        }
    }
}
