//
// Swiftfin is subject to the terms of the Mozilla Public
// License, v2.0. If a copy of the MPL was not distributed with this
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2023 Jellyfin & Jellyfin Contributors
//

import SwiftUI

struct FocusGuideModifier: ViewModifier {

    @FocusState
    var focusDirection: FocusDirection?
    @EnvironmentObject
    var focusGuide: FocusGuide

    let focusConstructor: FocusConstructor
    let onContentFocus: (() -> Void)?

    let debug = false

    func body(content: Content) -> some View {
        VStack(spacing: 0) {

            Color(debug ? .red : .clear)
                .frame(height: 1)
                .if(focusConstructor.topTarget != nil, transform: { boundary in
                    boundary.focusable()
                })
                .focused($focusDirection, equals: .top)

            HStack(spacing: 0) {
                Color(debug ? .red : .clear)
                    .frame(width: 1)
                    .if(focusConstructor.leftTarget != nil, transform: { boundary in
                        boundary.focusable()
                    })
                    .focused($focusDirection, equals: .left)

                content
                    .focused($focusDirection, equals: .content)

                Color(debug ? .red : .clear)
                    .frame(width: 1)
                    .if(focusConstructor.rightTarget != nil, transform: { boundary in
                        boundary.focusable()
                    })
                    .focused($focusDirection, equals: .right)
            }

            Color(debug ? .red : .clear)
                .frame(height: 1)
                .if(focusConstructor.bottomTarget != nil, transform: { boundary in
                    boundary.focusable()
                })
                .focused($focusDirection, equals: .bottom)
        }
        .onChange(of: focusDirection) { focusDirection in
            guard let focusDirection = focusDirection else { return }
            switch focusDirection {
            case .top:
                focusGuide.transition(to: focusConstructor.topTarget!)
            case .bottom:
                focusGuide.transition(to: focusConstructor.bottomTarget!)
            case .left:
                focusGuide.transition(to: focusConstructor.leftTarget!)
            case .right:
                focusGuide.transition(to: focusConstructor.rightTarget!)
            case .content: ()
            }
        }
        .onChange(of: focusGuide.focusedTag) { newTag in
            if newTag == focusConstructor.tag {
                if let onContentFocus = onContentFocus {
                    onContentFocus()
                } else {
                    focusDirection = .content
                }
            }
        }
    }
}

extension View {
    func focusGuide(
        _ focusGuide: FocusGuide,
        tag: String,
        onContentFocus: (() -> Void)? = nil,
        top: String? = nil,
        bottom: String? = nil,
        left: String? = nil,
        right: String? = nil
    ) -> some View {
        let focusConstructor = FocusConstructor(
            tag: tag,
            topTarget: top,
            bottomTarget: bottom,
            leftTarget: left,
            rightTarget: right
        )
        return modifier(FocusGuideModifier(focusConstructor: focusConstructor, onContentFocus: onContentFocus))
            .environmentObject(focusGuide)
    }
}

enum FocusDirection: String {
    case top
    case bottom
    case content
    case left
    case right
}

struct FocusConstructor {

    let tag: String
    let topTarget: String?
    let bottomTarget: String?
    let leftTarget: String?
    let rightTarget: String?

    init(
        tag: String,
        topTarget: String?,
        bottomTarget: String?,
        leftTarget: String?,
        rightTarget: String?
    ) {
        self.tag = tag
        self.topTarget = topTarget
        self.bottomTarget = bottomTarget
        self.leftTarget = leftTarget
        self.rightTarget = rightTarget
    }
}

class FocusGuide: ObservableObject {

    @Published
    private(set) var focusedTag: String?

    private(set) var lastFocusedTag: String?

    func transition(to tag: String?) {
        lastFocusedTag = focusedTag
        focusedTag = tag
    }
}
