import Observation
import SwiftUI

enum PhoneRoute: Hashable {
    case chat(String)
    case search(String)
    case draftChat
    case draftSearch
    case settings

    static func from(selection: SidebarSelection) -> PhoneRoute {
        switch selection {
        case let .chat(chatID):
            return .chat(chatID)
        case let .search(searchID):
            return .search(searchID)
        case .settings:
            return .settings
        }
    }
}

struct SybilPhoneShellView: View {
    @Bindable var viewModel: SybilViewModel
    @State private var path: [PhoneRoute] = []
    @Environment(\.scenePhase) private var scenePhase
    @State private var shouldRefreshOnForeground = false
    @State private var composerFocusRequest = 0
    @State private var phoneStackWidth: CGFloat = BackSwipeMetrics.referenceWidth
    @State private var backSwipeOffset: CGFloat = 0
    @State private var backSwipeCompletionOffset: CGFloat = 0
    @State private var backSwipeIsActive = false
    @State private var backSwipeIsCompleting = false
    @State private var backSwipeHasLatched = false

    private var canRecognizeBackSwipe: Bool {
        !path.isEmpty && !backSwipeIsCompleting
    }

    private var backSwipeVisualOffset: CGFloat {
        backSwipeOffset + backSwipeCompletionOffset
    }

    var body: some View {
        GeometryReader { proxy in
            ZStack(alignment: .topLeading) {
                SybilPhoneSidebarRoot(viewModel: viewModel, path: $path)
                    .safeAreaInset(edge: .top, spacing: 0) {
                        phoneRootTopBar
                    }
                    .zIndex(0)

                if let route = path.last {
                    SybilPhoneDestinationView(
                        viewModel: viewModel,
                        composerFocusRequest: $composerFocusRequest,
                        route: route,
                        onRequestBack: requestBack,
                        onRequestNewChat: startNewChatFromDestination
                    )
                    .background(SybilTheme.background)
                    .offset(x: backSwipeVisualOffset)
                    .shadow(
                        color: backSwipeVisualOffset > 0 ? Color.black.opacity(0.34) : Color.clear,
                        radius: backSwipeVisualOffset > 0 ? 18 : 0,
                        x: -8,
                        y: 0
                    )
                    .transition(.move(edge: .trailing))
                    .zIndex(1)
                    .background {
                        WorkspaceSwipePanInstaller(
                            direction: .right,
                            isEnabled: canRecognizeBackSwipe,
                            onBegan: { width in
                                beginBackSwipe(containerWidth: width)
                            },
                            onChanged: { translationX, width in
                                updateBackSwipe(with: translationX, containerWidth: width)
                            },
                            onEnded: { translationX, width, velocityX, didFinish in
                                finishBackSwipe(
                                    translationX: translationX,
                                    containerWidth: width,
                                    velocityX: velocityX,
                                    didFinish: didFinish
                                )
                            }
                        )
                        .frame(maxWidth: .infinity, maxHeight: .infinity)
                    }
                }
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
            .onAppear {
                updatePhoneStackWidth(proxy.size.width)
            }
            .onChange(of: proxy.size.width) { _, width in
                updatePhoneStackWidth(width)
            }
        }
        .tint(SybilTheme.primary)
        .animation(.easeOut(duration: 0.22), value: path.last)
        .onChange(of: path) { _, nextPath in
            guard nextPath.isEmpty else {
                return
            }
            resetBackSwipe(animated: false)
        }
        .onChange(of: scenePhase) { _, nextPhase in
            switch nextPhase {
            case .background:
                shouldRefreshOnForeground = true
                viewModel.markAppInactiveForNetwork()
            case .active:
                viewModel.markAppActiveForNetwork()
                guard shouldRefreshOnForeground else {
                    return
                }
                shouldRefreshOnForeground = false
                Task {
                    await viewModel.refreshAfterAppBecameActive(
                        refreshCollections: path.isEmpty,
                        refreshSelection: !path.isEmpty && viewModel.hasRefreshableSelection
                    )
                }
            case .inactive:
                shouldRefreshOnForeground = true
                viewModel.markAppInactiveForNetwork()
            @unknown default:
                break
            }
        }
    }

    private var phoneRootTopBar: some View {
        HStack {
            SybilWordmark(size: 21)
            Spacer()
        }
        .padding(.horizontal, 16)
        .padding(.top, 10)
        .padding(.bottom, 12)
        .background {
            SybilTheme.panelGradient
                .ignoresSafeArea(edges: .top)
        }
    }

    private func updatePhoneStackWidth(_ width: CGFloat) {
        phoneStackWidth = max(width, 1)
    }

    private func requestBack(animateNavigation: Bool = true) {
        guard !path.isEmpty, !backSwipeIsCompleting else {
            return
        }

        if animateNavigation {
            Task {
                await completeBackSwipe(containerWidth: phoneStackWidth, releaseVelocityX: 0)
            }
        } else {
            popRoute(disablesAnimations: true)
            resetBackSwipe(animated: false)
        }
    }

    private func startNewChatFromDestination() {
        viewModel.startNewChat()
        composerFocusRequest += 1
        replaceTopRoute(with: .draftChat)
    }

    private func replaceTopRoute(with route: PhoneRoute) {
        if path.isEmpty {
            withAnimation(.easeOut(duration: 0.22)) {
                path = [route]
            }
        } else {
            path[path.index(before: path.endIndex)] = route
        }
    }

    private func popRoute(disablesAnimations: Bool) {
        let pop = {
            guard !path.isEmpty else {
                return
            }
            _ = path.removeLast()
        }

        if disablesAnimations {
            var transaction = Transaction()
            transaction.disablesAnimations = true
            withTransaction(transaction) {
                pop()
            }
        } else {
            withAnimation(.easeOut(duration: 0.22)) {
                pop()
            }
        }
    }

    private func beginBackSwipe(containerWidth: CGFloat) {
        let update = {
            backSwipeIsActive = true
            backSwipeHasLatched = false
        }

        var transaction = Transaction()
        transaction.disablesAnimations = true
        withTransaction(transaction, update)
    }

    private func updateBackSwipe(with rawTranslation: CGFloat, containerWidth: CGFloat) {
        let nextOffset = BackSwipeMetrics.clampedOffset(for: rawTranslation, width: containerWidth)
        let nextLatched = BackSwipeMetrics.isLatched(
            offset: nextOffset,
            width: containerWidth,
            isCurrentlyLatched: backSwipeHasLatched
        )

        var transaction = Transaction()
        transaction.disablesAnimations = true
        withTransaction(transaction) {
            backSwipeOffset = nextOffset
            backSwipeHasLatched = nextLatched
        }
    }

    private func finishBackSwipe(
        translationX: CGFloat,
        containerWidth: CGFloat,
        velocityX: CGFloat,
        didFinish: Bool
    ) {
        guard backSwipeIsActive else {
            resetBackSwipe(animated: false)
            return
        }

        let finalOffset = BackSwipeMetrics.clampedOffset(for: translationX, width: containerWidth)
        let finalLatched = BackSwipeMetrics.isLatched(
            offset: finalOffset,
            width: containerWidth,
            isCurrentlyLatched: backSwipeHasLatched
        )
        updateBackSwipe(with: translationX, containerWidth: containerWidth)

        if didFinish && BackSwipeMetrics.shouldComplete(
            offset: finalOffset,
            velocityX: velocityX,
            width: containerWidth,
            isLatched: finalLatched
        ) {
            Task {
                await completeBackSwipe(containerWidth: containerWidth, releaseVelocityX: velocityX)
            }
            return
        }

        resetBackSwipe(animated: true, velocityX: velocityX)
    }

    @MainActor
    private func completeBackSwipe(containerWidth: CGFloat, releaseVelocityX: CGFloat) async {
        guard !path.isEmpty else {
            resetBackSwipe(animated: false)
            return
        }
        guard !backSwipeIsCompleting else {
            return
        }

        backSwipeIsCompleting = true
        let targetOffset = BackSwipeMetrics.completionTargetOffset(for: containerWidth)

        withAnimation(
            BackSwipeMetrics.springAnimation(
                currentOffset: backSwipeOffset,
                targetOffset: targetOffset,
                velocityX: releaseVelocityX
            )
        ) {
            backSwipeCompletionOffset = targetOffset - backSwipeOffset
        }

        try? await Task.sleep(for: .milliseconds(BackSwipeMetrics.completionAnimationDelayMs))
        popRoute(disablesAnimations: true)
        resetBackSwipe(animated: false)
    }

    private func resetBackSwipe(animated: Bool, velocityX: CGFloat = 0) {
        let currentOffset = backSwipeOffset + backSwipeCompletionOffset
        let reset = {
            backSwipeOffset = 0
            backSwipeCompletionOffset = 0
            backSwipeIsActive = false
            backSwipeIsCompleting = false
            backSwipeHasLatched = false
        }

        if animated {
            withAnimation(
                BackSwipeMetrics.springAnimation(
                    currentOffset: currentOffset,
                    targetOffset: 0,
                    velocityX: velocityX
                )
            ) {
                reset()
            }
        } else {
            reset()
        }
    }
}

private struct SybilPhoneSidebarRoot: View {
    @Bindable var viewModel: SybilViewModel
    @Binding var path: [PhoneRoute]
    @State private var openingSelection: SidebarSelection?
    @State private var openingRequestID: UUID?

    private var highlightedSelection: SidebarSelection? {
        if let openingSelection {
            return openingSelection
        }

        guard let route = path.last else {
            return nil
        }

        switch route {
        case let .chat(chatID):
            return .chat(chatID)
        case let .search(searchID):
            return .search(searchID)
        case .draftChat, .draftSearch, .settings:
            return nil
        }
    }

    var body: some View {
        VStack(spacing: 0) {
            if let errorMessage = viewModel.errorMessage {
                Text(errorMessage)
                    .font(.sybil(.footnote))
                    .foregroundStyle(SybilTheme.danger)
                    .frame(maxWidth: .infinity, alignment: .leading)
                    .padding(.horizontal, 12)
                    .padding(.vertical, 10)

                Divider()
                    .overlay(SybilTheme.border)
            }

            if viewModel.isLoadingCollections && viewModel.sidebarItems.isEmpty {
                VStack(alignment: .leading, spacing: 8) {
                    ProgressView()
                        .tint(SybilTheme.primary)
                    Text("Loading conversations…")
                        .font(.sybil(.footnote))
                        .foregroundStyle(SybilTheme.textMuted)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
                .padding(16)
            } else if viewModel.sidebarItems.isEmpty {
                VStack(spacing: 10) {
                    Image(systemName: "message.badge")
                        .font(.system(size: 20, weight: .medium))
                        .foregroundStyle(SybilTheme.textMuted)
                    Text("Start a chat or run your first search.")
                        .font(.sybil(.footnote))
                        .multilineTextAlignment(.center)
                        .foregroundStyle(SybilTheme.textMuted)
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .padding(16)
            } else {
                ScrollView {
                    LazyVStack(alignment: .leading, spacing: 0) {
                        ForEach(viewModel.sidebarItems) { item in
                            Button {
                                open(item.selection)
                            } label: {
                                VStack(spacing: 0.0) {
                                    SybilPhoneSidebarRow(item: item)
                                    Divider()
                                }
                            }
                            .buttonStyle(
                                SybilPhoneSidebarRowButtonStyle(
                                    isHighlighted: highlightedSelection == item.selection
                                )
                            )
                            .contextMenu {
                                Button(role: .destructive) {
                                    Task {
                                        await viewModel.deleteItem(item.selection)
                                    }
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                            }
                        }
                    }
                }
                .refreshable {
                    await viewModel.refreshVisibleContent(
                        refreshCollections: true,
                        refreshSelection: false
                    )
                }
            }
        }
        .background(SybilTheme.panelGradient)
        .safeAreaInset(edge: .bottom, spacing: 0) {
            bottomToolbar
        }
    }

    private var bottomToolbar: some View {
        VStack(spacing: 0) {
            Divider()
                .overlay(SybilTheme.border)

            HStack(spacing: 12) {
                toolbarIconButton(systemImage: "gearshape", accessibilityLabel: "Settings") {
                    clearOpeningSelection()
                    showRoute(.settings)
                }

                Spacer()

                toolbarIconButton(systemImage: "magnifyingglass", accessibilityLabel: "New search") {
                    clearOpeningSelection()
                    viewModel.startNewSearch()
                    showRoute(.draftSearch)
                }

                toolbarIconButton(systemImage: "plus", accessibilityLabel: "New chat", isPrimary: true) {
                    clearOpeningSelection()
                    viewModel.startNewChat()
                    showRoute(.draftChat)
                }
            }
            .padding(.horizontal, 18)
            .padding(.vertical, 10)
            .background(SybilTheme.panelGradient)
        }
    }

    private func toolbarIconButton(
        systemImage: String,
        accessibilityLabel: String,
        isPrimary: Bool = false,
        action: @escaping () -> Void
    ) -> some View {
        Button(action: action) {
            Image(systemName: systemImage)
                .font(.system(size: 18, weight: .semibold))
                .foregroundStyle(isPrimary ? SybilTheme.text : SybilTheme.textMuted)
                .frame(width: 42, height: 42)
                .background(
                    Circle()
                        .fill(
                            isPrimary
                                ? AnyShapeStyle(SybilTheme.primaryGradient)
                                : AnyShapeStyle(SybilTheme.surface.opacity(0.78))
                        )
                )
                .overlay(
                    Circle()
                        .stroke(isPrimary ? SybilTheme.primary.opacity(0.42) : SybilTheme.border.opacity(0.76), lineWidth: 1)
                )
        }
        .buttonStyle(.plain)
        .accessibilityLabel(accessibilityLabel)
    }

    private func clearOpeningSelection() {
        openingRequestID = nil
        openingSelection = nil
    }

    private func showRoute(_ route: PhoneRoute) {
        withAnimation(.easeOut(duration: 0.22)) {
            path = [route]
        }
    }

    private func open(_ selection: SidebarSelection) {
        guard openingSelection != selection else {
            return
        }

        let requestID = UUID()
        openingRequestID = requestID
        openingSelection = selection
        Task {
            await viewModel.selectForNavigation(selection)
            guard openingRequestID == requestID else {
                return
            }
            showRoute(PhoneRoute.from(selection: selection))
            openingRequestID = nil
            openingSelection = nil
        }
    }
}

private struct SybilPhoneSidebarRowIsActiveKey: EnvironmentKey {
    static let defaultValue = false
}

private extension EnvironmentValues {
    var sybilPhoneSidebarRowIsActive: Bool {
        get { self[SybilPhoneSidebarRowIsActiveKey.self] }
        set { self[SybilPhoneSidebarRowIsActiveKey.self] = newValue }
    }
}

private struct SybilPhoneSidebarRowButtonStyle: ButtonStyle {
    var isHighlighted: Bool

    func makeBody(configuration: Configuration) -> some View {
        configuration.label
            .environment(\.sybilPhoneSidebarRowIsActive, isHighlighted || configuration.isPressed)
    }
}

private struct SybilPhoneSidebarRow: View {
    @Environment(\.sybilPhoneSidebarRowIsActive) private var isHighlighted
    var item: SidebarItem

    var body: some View {
        let leadingWidth = 22.0
        
        VStack(alignment: .leading, spacing: 8) {
            HStack(spacing: 8) {
                Image(systemName: item.kind == .chat ? "message" : "globe")
                    .font(.system(size: 12, weight: .semibold))
                    .foregroundStyle(isHighlighted ? SybilTheme.accent : SybilTheme.textMuted)
                    .frame(width: leadingWidth, height: leadingWidth)
                    .background(
                        Rectangle()
                            .fill(isHighlighted ? SybilTheme.accent.opacity(0.12) : SybilTheme.surface.opacity(0.72))
                            
                    )

                Text(item.title)
                    .font(.sybil(.subheadline, weight: .semibold))
                    .lineLimit(1)
                    .layoutPriority(1)

                Spacer(minLength: 8)

                if item.isRunning {
                    SybilSidebarActivityIndicator()
                }
            }

            HStack(spacing: 8) {
                Spacer()
                    .frame(width: leadingWidth)

                Text(item.updatedAt.sybilRelativeLabel)
                    .font(.sybil(.caption2))
                    .foregroundStyle(SybilTheme.textMuted)

                if let initiated = item.initiatedLabel {
                    Spacer(minLength: 0)
                    Text(initiated)
                        .font(.sybil(.caption2))
                        .foregroundStyle(SybilTheme.textMuted.opacity(0.88))
                        .lineLimit(1)
                        .multilineTextAlignment(.trailing)
                        .frame(maxWidth: .infinity, alignment: .trailing)
                }
            }
        }
        .foregroundStyle(SybilTheme.text)
        .padding(18.0)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            Rectangle()
                .fill(
                    isHighlighted
                        ? SybilTheme.selectedRowGradient
                        : LinearGradient(colors: [SybilTheme.surface.opacity(0.56), SybilTheme.surface.opacity(0.36)], startPoint: .topLeading, endPoint: .bottomTrailing)
                )
        )

    }
}

private struct SybilPhoneDestinationView: View {
    @Bindable var viewModel: SybilViewModel
    @Binding var composerFocusRequest: Int
    let route: PhoneRoute
    let onRequestBack: (_ animateNavigation: Bool) -> Void
    let onRequestNewChat: () -> Void

    var body: some View {
        SybilWorkspaceView(
            viewModel: viewModel,
            composerFocusRequest: composerFocusRequest,
            onRequestBack: onRequestBack,
            onRequestNewChat: onRequestNewChat
        )
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
        .task(id: route) {
            applyRoute()
        }
    }

    private func applyRoute() {
        switch route {
        case let .chat(chatID):
            guard viewModel.draftKind != nil || viewModel.selectedItem != .chat(chatID) else {
                return
            }
            viewModel.select(.chat(chatID))
        case let .search(searchID):
            guard viewModel.draftKind != nil || viewModel.selectedItem != .search(searchID) else {
                return
            }
            viewModel.select(.search(searchID))
        case .draftChat:
            viewModel.startNewChat()
        case .draftSearch:
            viewModel.startNewSearch()
        case .settings:
            viewModel.openSettings()
        }
    }
}
