import DesignSystem
import Env
import Models
import Network
import Shimmer
import SwiftUI

@MainActor
public struct StatusDetailView: View {
  @Environment(Theme.self) private var theme
  @Environment(CurrentAccount.self) private var currentAccount
  @Environment(StreamWatcher.self) private var watcher
  @Environment(Client.self) private var client
  @Environment(RouterPath.self) private var routerPath
  @Environment(\.isCompact) private var isCompact: Bool
  @Environment(UserPreferences.self) private var userPreferences: UserPreferences

  @State private var viewModel: StatusDetailViewModel

  @State private var isLoaded: Bool = false
  @State private var statusHeight: CGFloat = 0

  /// April 4th, 2023: Without explicit focus being set, VoiceOver will skip over a seemingly random number of elements on this screen when pushing in from the main timeline.
  /// By using ``@AccessibilityFocusState`` and setting focus once, we work around this issue.
  @AccessibilityFocusState private var initialFocusBugWorkaround: Bool

  public init(statusId: String) {
    _viewModel = .init(wrappedValue: .init(statusId: statusId))
  }

  public init(status: Status) {
    _viewModel = .init(wrappedValue: .init(status: status))
  }

  public init(remoteStatusURL: URL) {
    _viewModel = .init(wrappedValue: .init(remoteStatusURL: remoteStatusURL))
  }

  public var body: some View {
    GeometryReader { reader in
      ScrollViewReader { proxy in
        List {
          if isLoaded {
            topPaddingView
          }

          switch viewModel.state {
          case .loading:
            loadingDetailView

          case let .display(statuses):
            makeStatusesListView(statuses: statuses)

            if !isLoaded {
              loadingContextView
            }

          #if !os(visionOS)
            Rectangle()
              .foregroundColor(theme.secondaryBackgroundColor)
              .frame(minHeight: reader.frame(in: .local).size.height - statusHeight)
              .listRowSeparator(.hidden)
              .listRowBackground(theme.secondaryBackgroundColor)
              .listRowInsets(.init())
              .accessibilityHidden(true)
          #endif

          case .error:
            errorView
          }
        }
        .environment(\.defaultMinListRowHeight, 1)
        .listStyle(.plain)
        #if !os(visionOS)
        .scrollContentBackground(.hidden)
        .background(theme.primaryBackgroundColor)
        #endif
        .onChange(of: viewModel.scrollToId) { _, newValue in
          if let newValue {
            viewModel.scrollToId = nil
            proxy.scrollTo(newValue, anchor: .top)
          }
        }
        .onAppear {
          guard !isLoaded else { return }
          viewModel.client = client
          viewModel.routerPath = routerPath
          Task {
            let result = await viewModel.fetch()
            isLoaded = true

            if !result {
              if let url = viewModel.remoteStatusURL {
                await UIApplication.shared.open(url)
              }
              DispatchQueue.main.async {
                _ = routerPath.path.popLast()
              }
            }
          }
        }
      }
      .refreshable {
        Task {
          await viewModel.fetch()
        }
      }
      .onChange(of: watcher.latestEvent?.id) {
        guard let lastEvent = watcher.latestEvent else { return }
        viewModel.handleEvent(event: lastEvent, currentAccount: currentAccount.account)
      }
    }
    .navigationTitle(viewModel.title)
    .navigationBarTitleDisplayMode(.inline)
  }

  private func makeStatusesListView(statuses: [Status]) -> some View {
    ForEach(statuses) { status in
      let (indentationLevel, extraInsets) = viewModel.getIndentationLevel(id: status.id, maxIndent: userPreferences.getRealMaxIndent())
      let viewModel: StatusRowViewModel = .init(status: status,
                                                client: client,
                                                routerPath: routerPath,
                                                scrollToId: $viewModel.scrollToId)
      let isFocused = self.viewModel.statusId == status.id

      StatusRowView(viewModel: viewModel)
        .id(status.id + (status.editedAt?.asDate.description ?? ""))
        .environment(\.extraLeadingInset, !isCompact ? extraInsets : 0)
        .environment(\.indentationLevel, !isCompact ? indentationLevel : 0)
        .environment(\.isStatusFocused, isFocused)
        .overlay {
          if isFocused {
            GeometryReader { reader in
              VStack {}
                .onAppear {
                  statusHeight = reader.size.height
                }
            }
          }
        }
        #if !os(visionOS)
        .listRowBackground(viewModel.highlightRowColor)
        #endif
        .listRowInsets(.init(top: 12,
                             leading: .layoutPadding,
                             bottom: 12,
                             trailing: .layoutPadding))
    }
  }

  private var errorView: some View {
    ErrorView(title: "status.error.title",
              message: "status.error.message",
              buttonTitle: "action.retry")
    {
      Task {
        await viewModel.fetch()
      }
    }
    #if !os(visionOS)
    .listRowBackground(theme.primaryBackgroundColor)
    #endif
    .listRowSeparator(.hidden)
  }

  private var loadingDetailView: some View {
    ForEach(Status.placeholders()) { status in
      StatusRowView(viewModel: .init(status: status, client: client, routerPath: routerPath))
        .redacted(reason: .placeholder)
        .allowsHitTesting(false)
    }
  }

  private var loadingContextView: some View {
    HStack {
      Spacer()
      ProgressView()
      Spacer()
    }
    .frame(height: 50)
    .listRowSeparator(.hidden)
    #if !os(visionOS)
    .listRowBackground(theme.secondaryBackgroundColor)
    #endif
    .listRowInsets(.init())
  }

  private var topPaddingView: some View {
    HStack { EmptyView() }
      #if !os(visionOS)
      .listRowBackground(theme.primaryBackgroundColor)
      #endif
      .listRowSeparator(.hidden)
      .listRowInsets(.init())
      .frame(height: .layoutPadding)
      .accessibilityHidden(true)
  }
}
