<commit_before>import Usecase
import Combine
import Entity
import SwiftUI

public protocol SearchDependency {
  var searchProviderUsecase: SearchProviderUsecase { get }
  var searchImageUsecase: SearchImageUsecase { get }
}

public protocol SearchListener: AnyObject {
  
}

public protocol SearchViewControllable: ViewControllable {
  
}

public final class SearchController: SearchControllable, ViewControllerBuildable {
  
  private let searchProviderUsecase: SearchProviderUsecase
  private let searchImageUsecase: SearchImageUsecase
  
  private var state: SearchState {
    didSet { self.viewState.send(.stateArrived(self.state)) }
  }
  private let viewState: PassthroughSubject<SearchViewState, Never>
  
  private weak var viewController: SearchViewControllable?
  weak var listener: SearchListener?
  
  public init(dependency: SearchDependency, listener: SearchListener?) {
    self.searchProviderUsecase = dependency.searchProviderUsecase
    self.searchImageUsecase = dependency.searchImageUsecase
    self.state = SearchState(provider: dependency.searchProviderUsecase.query())
    self.viewState = PassthroughSubject()
    self.listener = listener
  }
  
  public func buildViewController() -> ViewControllable {
    let viewController = SearchViewController(initialState: self.state, controller: self)
    self.viewController = viewController
    return viewController
  }
  
  // MARK: - SearchControllable
  
  func activate(with viewController: SearchViewControllable) -> Observable<SearchViewState> {
    self.viewController = viewController
    return self.viewState.eraseToAnyPublisher()
  }
  
  func requestSearch(query: String?) {
    guard let query = query, query.hasElement else { return }
    self.state.query = query
    Task { [weak self] in
      self?.viewState.send(.loading(true))
      do {
        try await self?.search()
      } catch {
        self?.viewState.send(.errorArrived(.search(error)))
      }
      self?.viewState.send(.loading(false))
    }
  }
  
  func requestSearchMore() {
    Task { [weak self] in
      do {
        try await self?.searchMore()
      } catch {
        self?.viewState.send(.errorArrived(.searchMore(error)))
      }
    }
  }
  
  func requestToggleGIF() {
    self.state.searchAnimatedGIF.toggle()
    Task { [weak self] in
      self?.viewState.send(.loading(true))
      do {
        try await self?.search()
      } catch {
        self?.viewState.send(.errorArrived(.search(error)))
      }
      self?.viewState.send(.loading(false))
    }
  }
  
  func requestChangeSearchProvider(to newValue: SearchProvider) {
    self.state.provider = newValue
    Task { [weak self] in
      self?.viewState.send(.loading(true))
      do {
        try await self?.search()
      } catch {
        self?.viewState.send(.errorArrived(.search(error)))
      }
      self?.viewState.send(.loading(false))
    }
  }
  
  private func search() async throws {
    let result = try await self.searchImageUsecase.execute(
      query: self.state.query, next: nil, provider: self.state.provider
    )
    self.state.images = result.images
    self.state.next = result.next
  }
  
  private func searchMore() async throws {
    guard let next = self.state.next else { return }
    let result = try await self.searchImageUsecase.execute(
      query: self.state.query, next: next, provider: self.state.provider
    )
    self.state.images = result.images
    self.state.next = result.next
  }
}<commit_msg>Search. SearchMore의 반복 호출 문제 수정.<commit_after>import Usecase
import Combine
import Entity
import SwiftUI

public protocol SearchDependency {
  var searchProviderUsecase: SearchProviderUsecase { get }
  var searchImageUsecase: SearchImageUsecase { get }
}

public protocol SearchListener: AnyObject {
  
}

public protocol SearchViewControllable: ViewControllable {
  
}

public final class SearchController: SearchControllable, ViewControllerBuildable {
  
  private let searchProviderUsecase: SearchProviderUsecase
  private let searchImageUsecase: SearchImageUsecase
  
  private var state: SearchState {
    didSet { self.viewState.send(.stateArrived(self.state)) }
  }
  private let viewState: PassthroughSubject<SearchViewState, Never>
  
  private weak var viewController: SearchViewControllable?
  weak var listener: SearchListener?
  
  public init(dependency: SearchDependency, listener: SearchListener?) {
    self.searchProviderUsecase = dependency.searchProviderUsecase
    self.searchImageUsecase = dependency.searchImageUsecase
    self.state = SearchState(provider: dependency.searchProviderUsecase.query())
    self.viewState = PassthroughSubject()
    self.listener = listener
  }
  
  public func buildViewController() -> ViewControllable {
    let viewController = SearchViewController(initialState: self.state, controller: self)
    self.viewController = viewController
    return viewController
  }
  
  // MARK: - SearchControllable
  
  func activate(with viewController: SearchViewControllable) -> Observable<SearchViewState> {
    self.viewController = viewController
    return self.viewState.eraseToAnyPublisher()
  }
  
  func requestSearch(query: String?) {
    guard let query = query, query.hasElement else { return }
    self.state.query = query
    Task { [weak self] in
      self?.viewState.send(.loading(true))
      do {
        try await self?.search()
      } catch {
        self?.viewState.send(.errorArrived(.search(error)))
      }
      self?.viewState.send(.loading(false))
    }
  }
  
  func requestSearchMore() {
    Task { [weak self] in
      do {
        try await self?.searchMore()
      } catch {
        self?.viewState.send(.errorArrived(.searchMore(error)))
      }
    }
  }
  
  func requestToggleGIF() {
    self.state.searchAnimatedGIF.toggle()
    Task { [weak self] in
      self?.viewState.send(.loading(true))
      do {
        try await self?.search()
      } catch {
        self?.viewState.send(.errorArrived(.search(error)))
      }
      self?.viewState.send(.loading(false))
    }
  }
  
  func requestChangeSearchProvider(to newValue: SearchProvider) {
    self.state.provider = newValue
    Task { [weak self] in
      self?.viewState.send(.loading(true))
      do {
        try await self?.search()
      } catch {
        self?.viewState.send(.errorArrived(.search(error)))
      }
      self?.viewState.send(.loading(false))
    }
  }
  
  private func search() async throws {
    let result = try await self.searchImageUsecase.execute(
      query: self.state.query, next: nil, provider: self.state.provider
    )
    self.state.images = result.images
    self.state.next = result.next
  }
  
  private func searchMore() async throws {
    guard let next = self.state.next else { return }
    let result = try await self.searchImageUsecase.execute(
      query: self.state.query, next: next, provider: self.state.provider
    )
    self.state.images.append(contentsOf: result.images)
    self.state.next = result.next
  }
}
{'fraction_non_alphanumeric': 0.09806173725771716, 'fraction_numerical': 0.0, 'mean_word_length': 3.2579462102689485, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5522045', 'n_tokens_mistral': 2033, 'n_tokens_neox': 2027, 'n_words': 439}