import DesignSystem
import Env
import Models
import Network
import SwiftUI

@MainActor
struct EditFilterView: View {
  @Environment(\.dismiss) private var dismiss

  @Environment(Theme.self) private var theme
  @Environment(CurrentAccount.self) private var account
  @Environment(Client.self) private var client

  @State private var isSavingFilter: Bool = false
  @State private var filter: ServerFilter?
  @State private var title: String
  @State private var keywords: [ServerFilter.Keyword]
  @State private var newKeyword: String = ""
  @State private var contexts: [ServerFilter.Context]
  @State private var filterAction: ServerFilter.Action
  @State private var expiresAt: Date?
  @State private var expirySelection: Duration

  enum Fields {
    case title, newKeyword
  }

  @FocusState private var focusedField: Fields?

  private var data: ServerFilterData {
    let expiresIn: String? = switch expirySelection {
    case .infinite:
      "" // need to send an empty value in order for the server to clear this field in the filter
    case .custom:
      String(Int(expiresAt?.timeIntervalSince(Date()) ?? 0) + 50)
    default:
      String(expirySelection.rawValue + 50)
    }

    return ServerFilterData(title: title,
                            context: contexts,
                            filterAction: filterAction,
                            expiresIn: expiresIn)
  }

  private var canSave: Bool {
    !title.isEmpty
  }

  init(filter: ServerFilter?) {
    _filter = .init(initialValue: filter)
    _title = .init(initialValue: filter?.title ?? "")
    _keywords = .init(initialValue: filter?.keywords ?? [])
    _contexts = .init(initialValue: filter?.context ?? [.home])
    _filterAction = .init(initialValue: filter?.filterAction ?? .warn)
    _expiresAt = .init(initialValue: filter?.expiresAt?.asDate)
    _expirySelection = .init(initialValue: filter?.expiresAt == nil ? .infinite : .custom)
  }

  var body: some View {
    Form {
      titleSection
      if filter != nil {
        expirySection
        keywordsSection
        contextsSection
        filterActionView
      }
    }
    .navigationTitle(filter?.title ?? NSLocalizedString("filter.new", comment: ""))
    .navigationBarTitleDisplayMode(.inline)
    #if !os(visionOS)
    .scrollContentBackground(.hidden)
    .scrollDismissesKeyboard(.interactively)
    .background(theme.secondaryBackgroundColor)
    #endif
    .onAppear {
      if filter == nil {
        focusedField = .title
      }
    }
    .toolbar {
      ToolbarItem(placement: .navigationBarTrailing) {
        saveButton
      }
    }
  }

  private var expirySection: some View {
    Section("filter.edit.expiry") {
      Picker(selection: $expirySelection, label: Text("filter.edit.expiry.duration")) {
        ForEach(Duration.filterDurations(), id: \.rawValue) { duration in
          Text(duration.description).tag(duration)
        }
      }
      .onChange(of: expirySelection) { _, newValue in
        if newValue != .custom {
          expiresAt = Date(timeIntervalSinceNow: TimeInterval(newValue.rawValue))
        }
      }
      if expirySelection != .infinite {
        DatePicker("filter.edit.expiry.date-time",
                   selection: Binding<Date>(get: { expiresAt ?? Date() }, set: { expiresAt = $0 }),
                   displayedComponents: [.date, .hourAndMinute])
          .disabled(expirySelection != .custom)
      }
    }
    #if !os(visionOS)
    .listRowBackground(theme.primaryBackgroundColor)
    #endif
  }

  @ViewBuilder
  private var titleSection: some View {
    Section("filter.edit.title") {
      TextField("filter.edit.title", text: $title)
        .focused($focusedField, equals: .title)
        .onSubmit {
          Task {
            await saveFilter()
          }
        }
    }
    #if !os(visionOS)
    .listRowBackground(theme.primaryBackgroundColor)
    #endif

    if filter == nil, !title.isEmpty {
      Section {
        Button {
          Task {
            await saveFilter()
          }
        } label: {
          if isSavingFilter {
            ProgressView()
              .frame(maxWidth: .infinity)
          } else {
            Text("action.save")
              .frame(maxWidth: .infinity)
          }
        }
        .buttonStyle(.borderedProminent)
        .transition(.opacity)
      }
      #if !os(visionOS)
      .listRowBackground(theme.secondaryBackgroundColor)
      #endif
    }
  }

  private var keywordsSection: some View {
    Section("filter.edit.keywords") {
      ForEach(keywords) { keyword in
        HStack {
          Text(keyword.keyword)
          Spacer()
          Button {
            Task {
              await deleteKeyword(keyword: keyword)
            }
          } label: {
            Image(systemName: "trash")
              .tint(.red)
          }
        }
      }
      .onDelete { indexes in
        if let index = indexes.first {
          let keyword = keywords[index]
          Task {
            await deleteKeyword(keyword: keyword)
          }
        }
      }
      HStack {
        TextField("filter.edit.keywords.add", text: $newKeyword, axis: .horizontal)
          .focused($focusedField, equals: .newKeyword)
          .onSubmit {
            Task {
              await addKeyword(name: newKeyword)
              newKeyword = ""
              focusedField = .newKeyword
            }
          }
        Spacer()
        if !newKeyword.isEmpty {
          Button {
            Task {
              Task {
                await addKeyword(name: newKeyword)
                newKeyword = ""
              }
            }
          } label: {
            Image(systemName: "checkmark.circle.fill")
              .tint(.green)
          }
        }
      }
    }
    #if !os(visionOS)
    .listRowBackground(theme.primaryBackgroundColor)
    #endif
  }

  private var contextsSection: some View {
    Section("filter.edit.contexts") {
      ForEach(ServerFilter.Context.allCases, id: \.self) { context in
        Toggle(isOn: .init(get: {
          contexts.contains(where: { $0 == context })
        }, set: { _ in
          if let index = contexts.firstIndex(of: context) {
            contexts.remove(at: index)
          } else {
            contexts.append(context)
          }
          Task {
            await saveFilter()
          }
        })) {
          Label(context.name, systemImage: context.iconName)
        }
        .disabled(isSavingFilter)
      }
      #if !os(visionOS)
      .listRowBackground(theme.primaryBackgroundColor)
      #endif
    }
  }

  private var filterActionView: some View {
    Section("filter.edit.action") {
      Picker(selection: $filterAction) {
        ForEach(ServerFilter.Action.allCases, id: \.self) { filter in
          Text(filter.label)
            .id(filter)
        }
      } label: {
        EmptyView()
      }
      .onChange(of: filterAction) {
        Task {
          await saveFilter()
        }
      }
      .pickerStyle(.inline)
    }
    #if !os(visionOS)
    .listRowBackground(theme.primaryBackgroundColor)
    #endif
  }

  private var saveButton: some View {
    Button {
      Task {
        if !newKeyword.isEmpty {
          await addKeyword(name: newKeyword)
          newKeyword = ""
          focusedField = .newKeyword
        }
        await saveFilter()
        dismiss()
      }
    } label: {
      if isSavingFilter {
        ProgressView()
      } else {
        Text("action.save").bold()
      }
    }
    .disabled(!canSave)
  }

  private func saveFilter() async {
    do {
      isSavingFilter = true
      if let filter {
        self.filter = try await client.put(endpoint: ServerFilters.editFilter(id: filter.id, json: data),
                                           forceVersion: .v2)
      } else {
        let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data),
                                                            forceVersion: .v2)
        filter = newFilter
      }
    } catch {}
    isSavingFilter = false
  }

  private func addKeyword(name: String) async {
    guard let filterId = filter?.id else { return }
    isSavingFilter = true
    do {
      let keyword: ServerFilter.Keyword = try await
        client.post(endpoint: ServerFilters.addKeyword(filter: filterId,
                                                       keyword: name,
                                                       wholeWord: true),
                    forceVersion: .v2)
      keywords.append(keyword)
    } catch {}
    isSavingFilter = false
  }

  private func deleteKeyword(keyword: ServerFilter.Keyword) async {
    isSavingFilter = true
    do {
      let response = try await client.delete(endpoint: ServerFilters.removeKeyword(id: keyword.id),
                                             forceVersion: .v2)
      if response?.statusCode == 200 {
        keywords.removeAll(where: { $0.id == keyword.id })
      }
    } catch {}
    isSavingFilter = false
  }
}
