     1→import SwiftUI
     2→import MapKit
     3→
     4→/// Main view with map, category filter, and restaurant list
     5→struct MainView: View {
     6→    @EnvironmentObject var authService: AuthenticationService
     7→    @StateObject private var locationService = LocationService()
     8→    @StateObject private var restaurantService = RestaurantService()
     9→    @StateObject private var favoritesService = FavoritesService.shared
    10→
    11→    @State private var selectedRestaurant: Restaurant?
    12→    @State private var cameraPosition: MapCameraPosition = .automatic
    13→    @State private var showingFavorites = false
    14→    @State private var showingAIChat = false
    15→
    16→    var body: some View {
    17→        NavigationStack {
    18→            ZStack {
    19→                VStack(spacing: 0) {
    20→                    // Category filter
    21→                    categoryFilterSection
    22→
    23→                    // Map (top 35%)
    24→                    mapSection
    25→                        .frame(height: UIScreen.main.bounds.height * 0.35)
    26→
    27→                    // List (bottom)
    28→                    listSection
    29→                }
    30→
    31→                // Loading overlay
    32→                if restaurantService.isLoading || locationService.isLoading {
    33→                    loadingOverlay
    34→                }
    35→
    36→                // Floating AI button
    37→                floatingAIButton
    38→            }
    39→            .navigationTitle("FoodFinder")
    40→            .navigationBarTitleDisplayMode(.inline)
    41→            .toolbar {
    42→                ToolbarItem(placement: .topBarLeading) {
    43→                    Button {
    44→                        refreshLocation()
    45→                    } label: {
    46→                        Image(systemName: "arrow.clockwise")
    47→                    }
    48→                    .disabled(locationService.isLoading || restaurantService.isLoading)
    49→                }
    50→
    51→                ToolbarItem(placement: .topBarTrailing) {
    52→                    HStack(spacing: 16) {
    53→                        // Favorites button
    54→                        Button {
    55→                            showingFavorites = true
    56→                        } label: {
    57→                            Image(systemName: favoritesService.favorites.isEmpty ? "heart" : "heart.fill")
    58→                                .foregroundColor(favoritesService.favorites.isEmpty ? .primary : .red)
    59→                        }
    60→
    61→                        // User menu
    62→                        Menu {
    63→                            if let user = authService.currentUser {
    64→                                Text(user.displayName)
    65→                            }
    66→                            Divider()
    67→                            Button("Sign Out", role: .destructive) {
    68→                                authService.signOut()
    69→                            }
    70→                        } label: {
    71→                            Image(systemName: "person.circle")
    72→                        }
    73→                    }
    74→                }
    75→            }
    76→            .onAppear {
    77→                setupLocation()
    78→            }
    79→            .onChange(of: locationService.currentLocation) { _, newLocation in
    80→                if let location = newLocation {
    81→                    Task {
    82→                        await restaurantService.searchNearby(location: location)
    83→                        updateCameraPosition(to: location)
    84→                    }
    85→                }
    86→            }
    87→            .sheet(item: $selectedRestaurant) { restaurant in
    88→                RestaurantDetailView(restaurant: restaurant)
    89→                    .presentationDetents([.medium])
    90→            }
    91→            .sheet(isPresented: $showingFavorites) {
    92→                FavoritesView()
    93→            }
    94→            .sheet(isPresented: $showingAIChat) {
    95→                AIChatView(
    96→                    restaurants: restaurantService.restaurants,
    97→                    favorites: favoritesService.favorites,
    98→                    category: restaurantService.selectedCategory
    99→                )
   100→                .presentationDetents([.large])
   101→            }
   102→        }
   103→    }
   104→
   105→    // MARK: - Category Filter Section
   106→
   107→    private var categoryFilterSection: some View {
   108→        ScrollView(.horizontal, showsIndicators: false) {
   109→            HStack(spacing: 8) {
   110→                ForEach(FoodCategory.allCases) { category in
   111→                    CategoryChip(
   112→                        category: category,
   113→                        isSelected: restaurantService.selectedCategory == category
   114→                    ) {
   115→                        Task {
   116→                            await restaurantService.setCategory(category)
   117→                        }
   118→                    }
   119→                }
   120→            }
   121→            .padding(.horizontal)
   122→            .padding(.vertical, 8)
   123→        }
   124→        .background(Color(.systemBackground))
   125→    }
   126→
   127→    // MARK: - Map Section
   128→
   129→    private var mapSection: some View {
   130→        Map(position: $cameraPosition) {
   131→            // User location marker
   132→            UserAnnotation()
   133→
   134→            // Restaurant markers
   135→            ForEach(restaurantService.restaurants) { restaurant in
   136→                Annotation(restaurant.name, coordinate: restaurant.coordinate) {
   137→                    RestaurantAnnotationView(
   138→                        restaurant: restaurant,
   139→                        isSelected: selectedRestaurant?.id == restaurant.id
   140→                    )
   141→                    .onTapGesture {
   142→                        selectedRestaurant = restaurant
   143→                    }
   144→                }
   145→            }
   146→        }
   147→        .mapControls {
   148→            MapUserLocationButton()
   149→            MapCompass()
   150→        }
   151→    }
   152→
   153→    // MARK: - List Section
   154→
   155→    private var listSection: some View {
   156→        Group {
   157→            if locationService.isDenied {
   158→                locationDeniedView
   159→            } else if restaurantService.restaurants.isEmpty && !restaurantService.isLoading && !locationService.isLoading {
   160→                emptyStateView
   161→            } else {
   162→                restaurantList
   163→            }
   164→        }
   165→    }
   166→
   167→    private var restaurantList: some View {
   168→        List(restaurantService.restaurants) { restaurant in
   169→            RestaurantRowWithFavorite(
   170→                restaurant: restaurant,
   171→                isFavorite: favoritesService.isFavorite(restaurant),
   172→                onFavoriteToggle: {
   173→                    favoritesService.toggleFavorite(restaurant)
   174→                }
   175→            )
   176→            .contentShape(Rectangle())
   177→            .onTapGesture {
   178→                selectedRestaurant = restaurant
   179→            }
   180→        }
   181→        .listStyle(.plain)
   182→        .refreshable {
   183→            await refreshData()
   184→        }
   185→    }
   186→
   187→    private var emptyStateView: some View {
   188→        VStack(spacing: 16) {
   189→            Image(systemName: "fork.knife.circle")
   190→                .font(.system(size: 48))
   191→                .foregroundColor(.secondary)
   192→
   193→            Text("No \(restaurantService.selectedCategory.displayName.lowercased()) found nearby")
   194→                .font(.headline)
   195→
   196→            Text("Try a different category or location")
   197→                .font(.subheadline)
   198→                .foregroundColor(.secondary)
   199→
   200→            Button {
   201→                refreshLocation()
   202→            } label: {
   203→                Label("Refresh", systemImage: "arrow.clockwise")
   204→            }
   205→            .buttonStyle(.bordered)
   206→        }
   207→        .frame(maxWidth: .infinity, maxHeight: .infinity)
   208→        .background(Color(.systemGroupedBackground))
   209→    }
   210→
   211→    private var locationDeniedView: some View {
   212→        VStack(spacing: 16) {
   213→            Image(systemName: "location.slash")
   214→                .font(.system(size: 48))
   215→                .foregroundColor(.orange)
   216→
   217→            Text("Location Access Required")
   218→                .font(.headline)
   219→
   220→            Text("FoodFinder needs your location to find restaurants nearby")
   221→                .font(.subheadline)
   222→                .foregroundColor(.secondary)
   223→                .multilineTextAlignment(.center)
   224→                .padding(.horizontal)
   225→
   226→            Button("Open Settings") {
   227→                locationService.openSettings()
   228→            }
   229→            .buttonStyle(.borderedProminent)
   230→            .tint(.orange)
   231→        }
   232→        .frame(maxWidth: .infinity, maxHeight: .infinity)
   233→        .background(Color(.systemGroupedBackground))
   234→    }
   235→
   236→    private var loadingOverlay: some View {
   237→        VStack(spacing: 12) {
   238→            ProgressView()
   239→                .scaleEffect(1.2)
   240→
   241→            Text(locationService.isLoading ? "Getting location..." : "Finding \(restaurantService.selectedCategory.displayName.lowercased())...")
   242→                .font(.caption)
   243→                .foregroundColor(.secondary)
   244→        }
   245→        .padding(20)
   246→        .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
   247→    }
   248→
   249→    private var floatingAIButton: some View {
   250→        VStack {
   251→            Spacer()
   252→            HStack {
   253→                Spacer()
   254→                Button {
   255→                    showingAIChat = true
   256→                } label: {
   257→                    ZStack {
   258→                        Circle()
   259→                            .fill(Color.orange)
   260→                            .frame(width: 56, height: 56)
   261→                            .shadow(color: .orange.opacity(0.4), radius: 8, x: 0, y: 4)
   262→
   263→                        Image(systemName: "brain.head.profile")
   264→                            .font(.system(size: 24))
   265→                            .foregroundColor(.white)
   266→                    }
   267→                }
   268→                .padding(.trailing, 20)
   269→                .padding(.bottom, 20)
   270→            }
   271→        }
   272→    }
   273→
   274→    // MARK: - Methods
   275→
   276→    private func setupLocation() {
   277→        if locationService.needsPermission {
   278→            locationService.requestPermission()
   279→        } else if locationService.isAuthorized {
   280→            locationService.requestCurrentLocation()
   281→        }
   282→    }
   283→
   284→    private func refreshLocation() {
   285→        locationService.requestCurrentLocation()
   286→    }
   287→
   288→    private func refreshData() async {
   289→        if let location = locationService.currentLocation {
   290→            await restaurantService.refresh(location: location)
   291→        }
   292→    }
   293→
   294→    private func updateCameraPosition(to location: CLLocation) {
   295→        cameraPosition = .region(MKCoordinateRegion(
   296→            center: location.coordinate,
   297→            latitudinalMeters: 4000,
   298→            longitudinalMeters: 4000
   299→        ))
   300→    }
   301→}
   302→
   303→// MARK: - Category Chip Component
   304→
   305→struct CategoryChip: View {
   306→    let category: FoodCategory
   307→    let isSelected: Bool
   308→    let action: () -> Void
   309→
   310→    var body: some View {
   311→        Button(action: action) {
   312→            HStack(spacing: 6) {
   313→                Image(systemName: category.icon)
   314→                    .font(.system(size: 12))
   315→                Text(category.displayName)
   316→                    .font(.subheadline)
   317→                    .fontWeight(isSelected ? .semibold : .regular)
   318→            }
   319→            .padding(.horizontal, 12)
   320→            .padding(.vertical, 8)
   321→            .background(isSelected ? Color.orange : Color(.systemGray6))
   322→            .foregroundColor(isSelected ? .white : .primary)
   323→            .cornerRadius(20)
   324→        }
   325→        .buttonStyle(.plain)
   326→    }
   327→}
   328→
   329→// MARK: - Restaurant Row with Favorite
   330→
   331→struct RestaurantRowWithFavorite: View {
   332→    let restaurant: Restaurant
   333→    let isFavorite: Bool
   334→    let onFavoriteToggle: () -> Void
   335→
   336→    var body: some View {
   337→        HStack(spacing: 12) {
   338→            // Icon
   339→            ZStack {
   340→                Circle()
   341→                    .fill(Color.orange.opacity(0.1))
   342→                    .frame(width: 44, height: 44)
   343→
   344→                Image(systemName: "fork.knife")
   345→                    .foregroundColor(.orange)
   346→            }
   347→
   348→            // Info
   349→            VStack(alignment: .leading, spacing: 4) {
   350→                Text(restaurant.name)
   351→                    .font(.headline)
   352→                    .lineLimit(1)
   353→
   354→                if !restaurant.address.isEmpty {
   355→                    Text(restaurant.address)
   356→                        .font(.caption)
   357→                        .foregroundColor(.secondary)
   358→                        .lineLimit(1)
   359→                }
   360→            }
   361→
   362→            Spacer()
   363→
   364→            // Distance Badge
   365→            if !restaurant.formattedDistance.isEmpty {
   366→                Text(restaurant.formattedDistance)
   367→                    .font(.caption)
   368→                    .fontWeight(.medium)
   369→                    .foregroundColor(.secondary)
   370→                    .padding(.horizontal, 8)
   371→                    .padding(.vertical, 4)
   372→                    .background(Color(.systemGray6))
   373→                    .cornerRadius(8)
   374→            }
   375→
   376→            // Favorite Button
   377→            Button {
   378→                onFavoriteToggle()
   379→            } label: {
   380→                Image(systemName: isFavorite ? "heart.fill" : "heart")
   381→                    .foregroundColor(isFavorite ? .red : .gray)
   382→                    .font(.system(size: 18))
   383→            }
   384→            .buttonStyle(.plain)
   385→
   386→            // Chevron
   387→            Image(systemName: "chevron.right")
   388→                .font(.caption)
   389→                .foregroundColor(.secondary)
   390→        }
   391→        .padding(.vertical, 4)
   392→    }
   393→}
   394→
   395→// MARK: - Preview
   396→
   397→#Preview {
   398→    MainView()
   399→        .environmentObject(AuthenticationService.preview)
   400→}
   401→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI
import MapKit

/// Main view with map, category filter, and restaurant list
struct MainView: View {
    @EnvironmentObject var authService: AuthenticationService
    @StateObject private var locationService = LocationService()
    @StateObject private var restaurantService = RestaurantService()
    @StateObject private var favoritesService = FavoritesService.shared

    @State private var selectedRestaurant: Restaurant?
    @State private var cameraPosition: MapCameraPosition = .automatic
    @State private var showingFavorites = false
    @State private var showingAIChat = false

    var body: some View {
        NavigationStack {
            ZStack {
                VStack(spacing: 0) {
                    // Category filter
                    categoryFilterSection

                    // Map (top 35%)
                    mapSection
                        .frame(height: UIScreen.main.bounds.height * 0.35)

                    // List (bottom)
                    listSection
                }

                // Loading overlay
                if restaurantService.isLoading || locationService.isLoading {
                    loadingOverlay
                }

                // Floating AI button
                floatingAIButton
            }
            .navigationTitle("FoodFinder")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .topBarLeading) {
                    Button {
                        refreshLocation()
                    } label: {
                        Image(systemName: "arrow.clockwise")
                    }
                    .disabled(locationService.isLoading || restaurantService.isLoading)
                }

                ToolbarItem(placement: .topBarTrailing) {
                    HStack(spacing: 16) {
                        // Favorites button
                        Button {
                            showingFavorites = true
                        } label: {
                            Image(systemName: favoritesService.favorites.isEmpty ? "heart" : "heart.fill")
                                .foregroundColor(favoritesService.favorites.isEmpty ? .primary : .red)
                        }

                        // User menu
                        Menu {
                            if let user = authService.currentUser {
                                Text(user.displayName)
                            }
                            Divider()
                            Button("Sign Out", role: .destructive) {
                                authService.signOut()
                            }
                        } label: {
                            Image(systemName: "person.circle")
                        }
                    }
                }
            }
            .onAppear {
                setupLocation()
            }
            .onChange(of: locationService.currentLocation) { _, newLocation in
                if let location = newLocation {
                    Task {
                        await restaurantService.searchNearby(location: location)
                        updateCameraPosition(to: location)
                    }
                }
            }
            .sheet(item: $selectedRestaurant) { restaurant in
                RestaurantDetailView(restaurant: restaurant)
                    .presentationDetents([.medium])
            }
            .sheet(isPresented: $showingFavorites) {
                FavoritesView()
            }
            .sheet(isPresented: $showingAIChat) {
                AIChatView(
                    restaurants: restaurantService.restaurants,
                    favorites: favoritesService.favorites,
                    category: restaurantService.selectedCategory
                )
                .presentationDetents([.large])
            }
        }
    }

    // MARK: - Category Filter Section

    private var categoryFilterSection: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            HStack(spacing: 8) {
                ForEach(FoodCategory.allCases) { category in
                    CategoryChip(
                        category: category,
                        isSelected: restaurantService.selectedCategory == category
                    ) {
                        Task {
                            await restaurantService.setCategory(category)
                        }
                    }
                }
            }
            .padding(.horizontal)
            .padding(.vertical, 8)
        }
        .background(Color(.systemBackground))
    }

    // MARK: - Map Section

    private var mapSection: some View {
        Map(position: $cameraPosition) {
            // User location marker
            UserAnnotation()

            // Restaurant markers
            ForEach(restaurantService.restaurants) { restaurant in
                Annotation(restaurant.name, coordinate: restaurant.coordinate) {
                    RestaurantAnnotationView(
                        restaurant: restaurant,
                        isSelected: selectedRestaurant?.id == restaurant.id
                    )
                    .onTapGesture {
                        selectedRestaurant = restaurant
                    }
                }
            }
        }
        .mapControls {
            MapUserLocationButton()
            MapCompass()
        }
    }

    // MARK: - List Section

    private var listSection: some View {
        Group {
            if locationService.isDenied {
                locationDeniedView
            } else if restaurantService.restaurants.isEmpty && !restaurantService.isLoading && !locationService.isLoading {
                emptyStateView
            } else {
                restaurantList
            }
        }
    }

    private var restaurantList: some View {
        List(restaurantService.restaurants) { restaurant in
            RestaurantRowWithFavorite(
                restaurant: restaurant,
                isFavorite: favoritesService.isFavorite(restaurant),
                onFavoriteToggle: {
                    favoritesService.toggleFavorite(restaurant)
                }
            )
            .contentShape(Rectangle())
            .onTapGesture {
                selectedRestaurant = restaurant
            }
        }
        .listStyle(.plain)
        .refreshable {
            await refreshData()
        }
    }

    private var emptyStateView: some View {
        VStack(spacing: 16) {
            Image(systemName: "fork.knife.circle")
                .font(.system(size: 48))
                .foregroundColor(.secondary)

            Text("No \(restaurantService.selectedCategory.displayName.lowercased()) found nearby")
                .font(.headline)

            Text("Try a different category or location")
                .font(.subheadline)
                .foregroundColor(.secondary)

            Button {
                refreshLocation()
            } label: {
                Label("Refresh", systemImage: "arrow.clockwise")
            }
            .buttonStyle(.bordered)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(.systemGroupedBackground))
    }

    private var locationDeniedView: some View {
        VStack(spacing: 16) {
            Image(systemName: "location.slash")
                .font(.system(size: 48))
                .foregroundColor(.orange)

            Text("Location Access Required")
                .font(.headline)

            Text("FoodFinder needs your location to find restaurants nearby")
                .font(.subheadline)
                .foregroundColor(.secondary)
                .multilineTextAlignment(.center)
                .padding(.horizontal)

            Button("Open Settings") {
                locationService.openSettings()
            }
            .buttonStyle(.borderedProminent)
            .tint(.orange)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(.systemGroupedBackground))
    }

    private var loadingOverlay: some View {
        VStack(spacing: 12) {
            ProgressView()
                .scaleEffect(1.2)

            Text(locationService.isLoading ? "Getting location..." : "Finding \(restaurantService.selectedCategory.displayName.lowercased())...")
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .padding(20)
        .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 12))
    }

    private var floatingAIButton: some View {
        VStack {
            Spacer()
            HStack {
                Spacer()
                Button {
                    showingAIChat = true
                } label: {
                    ZStack {
                        Circle()
                            .fill(Color.orange)
                            .frame(width: 56, height: 56)
                            .shadow(color: .orange.opacity(0.4), radius: 8, x: 0, y: 4)

                        Image(systemName: "brain.head.profile")
                            .font(.system(size: 24))
                            .foregroundColor(.white)
                    }
                }
                .padding(.trailing, 20)
                .padding(.bottom, 20)
            }
        }
    }

    // MARK: - Methods

    private func setupLocation() {
        if locationService.needsPermission {
            locationService.requestPermission()
        } else if locationService.isAuthorized {
            locationService.requestCurrentLocation()
        }
    }

    private func refreshLocation() {
        locationService.requestCurrentLocation()
    }

    private func refreshData() async {
        if let location = locationService.currentLocation {
            await restaurantService.refresh(location: location)
        }
    }

    private func updateCameraPosition(to location: CLLocation) {
        cameraPosition = .region(MKCoordinateRegion(
            center: location.coordinate,
            latitudinalMeters: 4000,
            longitudinalMeters: 4000
        ))
    }
}

// MARK: - Category Chip Component

struct CategoryChip: View {
    let category: FoodCategory
    let isSelected: Bool
    let action: () -> Void

    var body: some View {
        Button(action: action) {
            HStack(spacing: 6) {
                Image(systemName: category.icon)
                    .font(.system(size: 12))
                Text(category.displayName)
                    .font(.subheadline)
                    .fontWeight(isSelected ? .semibold : .regular)
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 8)
            .background(isSelected ? Color.orange : Color(.systemGray6))
            .foregroundColor(isSelected ? .white : .primary)
            .cornerRadius(20)
        }
        .buttonStyle(.plain)
    }
}

// MARK: - Restaurant Row with Favorite

struct RestaurantRowWithFavorite: View {
    let restaurant: Restaurant
    let isFavorite: Bool
    let onFavoriteToggle: () -> Void

    var body: some View {
        HStack(spacing: 12) {
            // Icon
            ZStack {
                Circle()
                    .fill(Color.orange.opacity(0.1))
                    .frame(width: 44, height: 44)

                Image(systemName: "fork.knife")
                    .foregroundColor(.orange)
            }

            // Info
            VStack(alignment: .leading, spacing: 4) {
                Text(restaurant.name)
                    .font(.headline)
                    .lineLimit(1)

                if !restaurant.address.isEmpty {
                    Text(restaurant.address)
                        .font(.caption)
                        .foregroundColor(.secondary)
                        .lineLimit(1)
                }
            }

            Spacer()

            // Distance Badge
            if !restaurant.formattedDistance.isEmpty {
                Text(restaurant.formattedDistance)
                    .font(.caption)
                    .fontWeight(.medium)
                    .foregroundColor(.secondary)
                    .padding(.horizontal, 8)
                    .padding(.vertical, 4)
                    .background(Color(.systemGray6))
                    .cornerRadius(8)
            }

            // Favorite Button
            Button {
                onFavoriteToggle()
            } label: {
                Image(systemName: isFavorite ? "heart.fill" : "heart")
                    .foregroundColor(isFavorite ? .red : .gray)
                    .font(.system(size: 18))
            }
            .buttonStyle(.plain)

            // Chevron
            Image(systemName: "chevron.right")
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .padding(.vertical, 4)
    }
}

// MARK: - Preview

#Preview {
    MainView()
        .environmentObject(AuthenticationService.preview)
}


Let me check a few more supporting files:


Bash

find /Users/neog/Apps/iOS/FoodFinder-iOS -name "*.swift" -type f | sort

List all Swift files in FoodFinder project


     1→import Foundation
     2→import CoreLocation
     3→import SwiftUI
     4→
     5→/// Service for handling device location using CoreLocation
     6→@MainActor
     7→class LocationService: NSObject, ObservableObject {
     8→
     9→    // MARK: - Published State
    10→
    11→    @Published var currentLocation: CLLocation?
    12→    @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
    13→    @Published var locationError: String?
    14→    @Published var isLoading: Bool = false
    15→
    16→    // MARK: - Private
    17→
    18→    private let locationManager = CLLocationManager()
    19→
    20→    // MARK: - Computed Properties
    21→
    22→    /// Check if location access is authorized
    23→    var isAuthorized: Bool {
    24→        authorizationStatus == .authorizedWhenInUse ||
    25→        authorizationStatus == .authorizedAlways
    26→    }
    27→
    28→    /// Check if permission needs to be requested
    29→    var needsPermission: Bool {
    30→        authorizationStatus == .notDetermined
    31→    }
    32→
    33→    /// Check if location access was denied
    34→    var isDenied: Bool {
    35→        authorizationStatus == .denied || authorizationStatus == .restricted
    36→    }
    37→
    38→    // MARK: - Initialization
    39→
    40→    override init() {
    41→        super.init()
    42→        locationManager.delegate = self
    43→        locationManager.desiredAccuracy = kCLLocationAccuracyBest
    44→        authorizationStatus = locationManager.authorizationStatus
    45→    }
    46→
    47→    // MARK: - Public Methods
    48→
    49→    /// Request location permission from user
    50→    func requestPermission() {
    51→        locationManager.requestWhenInUseAuthorization()
    52→    }
    53→
    54→    /// Start continuous location updates
    55→    func startUpdatingLocation() {
    56→        guard isAuthorized else {
    57→            locationError = "Location permission not granted"
    58→            return
    59→        }
    60→
    61→        isLoading = true
    62→        locationError = nil
    63→        locationManager.startUpdatingLocation()
    64→    }
    65→
    66→    /// Stop location updates
    67→    func stopUpdatingLocation() {
    68→        locationManager.stopUpdatingLocation()
    69→        isLoading = false
    70→    }
    71→
    72→    /// Request a single location update
    73→    func requestCurrentLocation() {
    74→        guard isAuthorized else {
    75→            if needsPermission {
    76→                requestPermission()
    77→            } else {
    78→                locationError = "Location access denied. Please enable in Settings."
    79→            }
    80→            return
    81→        }
    82→
    83→        isLoading = true
    84→        locationError = nil
    85→        locationManager.requestLocation()
    86→    }
    87→
    88→    /// Open device Settings app to location settings
    89→    func openSettings() {
    90→        guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
    91→        UIApplication.shared.open(settingsURL)
    92→    }
    93→}
    94→
    95→// MARK: - CLLocationManagerDelegate
    96→
    97→extension LocationService: CLLocationManagerDelegate {
    98→
    99→    nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
   100→        Task { @MainActor in
   101→            authorizationStatus = manager.authorizationStatus
   102→
   103→            if isAuthorized && currentLocation == nil {
   104→                startUpdatingLocation()
   105→            }
   106→        }
   107→    }
   108→
   109→    nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
   110→        Task { @MainActor in
   111→            isLoading = false
   112→            guard let location = locations.last else { return }
   113→            currentLocation = location
   114→            stopUpdatingLocation()  // For MVP, just get one location
   115→        }
   116→    }
   117→
   118→    nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
   119→        Task { @MainActor in
   120→            isLoading = false
   121→
   122→            if let clError = error as? CLError {
   123→                switch clError.code {
   124→                case .denied:
   125→                    locationError = "Location access denied"
   126→                case .locationUnknown:
   127→                    locationError = "Unable to determine location"
   128→                default:
   129→                    locationError = "Location error: \(error.localizedDescription)"
   130→                }
   131→            } else {
   132→                locationError = error.localizedDescription
   133→            }
   134→        }
   135→    }
   136→}
   137→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import CoreLocation
import SwiftUI

/// Service for handling device location using CoreLocation
@MainActor
class LocationService: NSObject, ObservableObject {

    // MARK: - Published State

    @Published var currentLocation: CLLocation?
    @Published var authorizationStatus: CLAuthorizationStatus = .notDetermined
    @Published var locationError: String?
    @Published var isLoading: Bool = false

    // MARK: - Private

    private let locationManager = CLLocationManager()

    // MARK: - Computed Properties

    /// Check if location access is authorized
    var isAuthorized: Bool {
        authorizationStatus == .authorizedWhenInUse ||
        authorizationStatus == .authorizedAlways
    }

    /// Check if permission needs to be requested
    var needsPermission: Bool {
        authorizationStatus == .notDetermined
    }

    /// Check if location access was denied
    var isDenied: Bool {
        authorizationStatus == .denied || authorizationStatus == .restricted
    }

    // MARK: - Initialization

    override init() {
        super.init()
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        authorizationStatus = locationManager.authorizationStatus
    }

    // MARK: - Public Methods

    /// Request location permission from user
    func requestPermission() {
        locationManager.requestWhenInUseAuthorization()
    }

    /// Start continuous location updates
    func startUpdatingLocation() {
        guard isAuthorized else {
            locationError = "Location permission not granted"
            return
        }

        isLoading = true
        locationError = nil
        locationManager.startUpdatingLocation()
    }

    /// Stop location updates
    func stopUpdatingLocation() {
        locationManager.stopUpdatingLocation()
        isLoading = false
    }

    /// Request a single location update
    func requestCurrentLocation() {
        guard isAuthorized else {
            if needsPermission {
                requestPermission()
            } else {
                locationError = "Location access denied. Please enable in Settings."
            }
            return
        }

        isLoading = true
        locationError = nil
        locationManager.requestLocation()
    }

    /// Open device Settings app to location settings
    func openSettings() {
        guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return }
        UIApplication.shared.open(settingsURL)
    }
}

// MARK: - CLLocationManagerDelegate

extension LocationService: CLLocationManagerDelegate {

    nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        Task { @MainActor in
            authorizationStatus = manager.authorizationStatus

            if isAuthorized && currentLocation == nil {
                startUpdatingLocation()
            }
        }
    }

    nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        Task { @MainActor in
            isLoading = false
            guard let location = locations.last else { return }
            currentLocation = location
            stopUpdatingLocation()  // For MVP, just get one location
        }
    }

    nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        Task { @MainActor in
            isLoading = false

            if let clError = error as? CLError {
                switch clError.code {
                case .denied:
                    locationError = "Location access denied"
                case .locationUnknown:
                    locationError = "Unable to determine location"
                default:
                    locationError = "Location error: \(error.localizedDescription)"
                }
            } else {
                locationError = error.localizedDescription
            }
        }
    }
}


Perfect! I see there are additional files. Let me read the remaining services:


     1→import Foundation
     2→import CoreLocation
     3→
     4→/// Service for calculating restaurant opportunity scores
     5→@MainActor
     6→class ScoringService: ObservableObject {
     7→
     8→    // MARK: - Singleton
     9→
    10→    static let shared = ScoringService()
    11→
    12→    // MARK: - Configuration
    13→
    14→    /// Maximum distance in meters for scoring (2km)
    15→    private let maxDistance: Double = 2000
    16→
    17→    // MARK: - Public Methods
    18→
    19→    /// Calculate opportunity score for a single restaurant
    20→    func calculateScore(
    21→        for restaurant: Restaurant,
    22→        context: UserContext
    23→    ) -> OpportunityScore {
    24→        var reasons: [String] = []
    25→
    26→        // Distance Score (0-100)
    27→        let distanceScore = calculateDistanceScore(restaurant.distanceFromUser)
    28→        if distanceScore > 80 {
    29→            reasons.append("Muito perto de você")
    30→        } else if distanceScore > 60 {
    31→            reasons.append("Distância confortável")
    32→        }
    33→
    34→        // Favorite Score (0-100)
    35→        let favoriteScore = calculateFavoriteScore(
    36→            restaurantId: restaurant.id,
    37→            favoriteIds: context.favoriteIds
    38→        )
    39→        if favoriteScore > 0 {
    40→            reasons.append("Nos seus favoritos")
    41→        }
    42→
    43→        // Context Score (0-100)
    44→        let contextScore = calculateContextScore(
    45→            restaurant: restaurant,
    46→            context: context
    47→        )
    48→        if contextScore > 70 {
    49→            reasons.append("Ótimo para \(context.timeContext.rawValue.lowercased())")
    50→        }
    51→
    52→        // WhatsApp bonus
    53→        if restaurant.hasWhatsApp {
    54→            reasons.append("WhatsApp disponível")
    55→        }
    56→
    57→        return OpportunityScore(
    58→            distanceScore: distanceScore,
    59→            favoriteScore: favoriteScore,
    60→            contextScore: contextScore,
    61→            reasons: reasons
    62→        )
    63→    }
    64→
    65→    /// Rank restaurants by opportunity score
    66→    func rankRestaurants(
    67→        _ restaurants: [Restaurant],
    68→        context: UserContext
    69→    ) -> [ScoredRestaurant] {
    70→        restaurants
    71→            .map { restaurant in
    72→                ScoredRestaurant(
    73→                    restaurant: restaurant,
    74→                    score: calculateScore(for: restaurant, context: context)
    75→                )
    76→            }
    77→            .sorted { $0.score.total > $1.score.total }
    78→    }
    79→
    80→    /// Get top restaurants by score
    81→    func topRestaurants(
    82→        _ restaurants: [Restaurant],
    83→        context: UserContext,
    84→        limit: Int = 5
    85→    ) -> [ScoredRestaurant] {
    86→        Array(rankRestaurants(restaurants, context: context).prefix(limit))
    87→    }
    88→
    89→    // MARK: - Private Score Calculations
    90→
    91→    private func calculateDistanceScore(_ distance: CLLocationDistance?) -> Double {
    92→        guard let distance = distance else { return 50 } // Unknown distance = average
    93→
    94→        if distance <= 0 { return 100 }
    95→        if distance >= maxDistance { return 0 }
    96→
    97→        // Linear interpolation: closer = higher score
    98→        let normalized = 1 - (distance / maxDistance)
    99→        return normalized * 100
   100→    }
   101→
   102→    private func calculateFavoriteScore(
   103→        restaurantId: UUID,
   104→        favoriteIds: Set<UUID>
   105→    ) -> Double {
   106→        favoriteIds.contains(restaurantId) ? 100 : 0
   107→    }
   108→
   109→    private func calculateContextScore(
   110→        restaurant: Restaurant,
   111→        context: UserContext
   112→    ) -> Double {
   113→        var score: Double = 50 // Base score
   114→
   115→        // Category match bonus
   116→        if context.selectedCategory != .all {
   117→            // Can't directly match category from MapKit, give neutral score
   118→            score += 10
   119→        }
   120→
   121→        // Time-appropriate category bonus
   122→        let suggestedCategories = context.timeContext.suggestedCategories
   123→
   124→        // Check if selected category matches time suggestions
   125→        if suggestedCategories.contains(context.selectedCategory) {
   126→            score += 20
   127→        }
   128→
   129→        // Phone available bonus (can contact easily)
   130→        if restaurant.phoneNumber != nil {
   131→            score += 10
   132→        }
   133→
   134→        // URL available bonus (can research)
   135→        if restaurant.url != nil {
   136→            score += 10
   137→        }
   138→
   139→        return min(score, 100)
   140→    }
   141→}
   142→
   143→// MARK: - Restaurant Extension
   144→
   145→extension Restaurant {
   146→    /// Calculate opportunity score with given context
   147→    @MainActor
   148→    func opportunityScore(context: UserContext) -> OpportunityScore {
   149→        ScoringService.shared.calculateScore(for: self, context: context)
   150→    }
   151→}
   152→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import CoreLocation

/// Service for calculating restaurant opportunity scores
@MainActor
class ScoringService: ObservableObject {

    // MARK: - Singleton

    static let shared = ScoringService()

    // MARK: - Configuration

    /// Maximum distance in meters for scoring (2km)
    private let maxDistance: Double = 2000

    // MARK: - Public Methods

    /// Calculate opportunity score for a single restaurant
    func calculateScore(
        for restaurant: Restaurant,
        context: UserContext
    ) -> OpportunityScore {
        var reasons: [String] = []

        // Distance Score (0-100)
        let distanceScore = calculateDistanceScore(restaurant.distanceFromUser)
        if distanceScore > 80 {
            reasons.append("Muito perto de você")
        } else if distanceScore > 60 {
            reasons.append("Distância confortável")
        }

        // Favorite Score (0-100)
        let favoriteScore = calculateFavoriteScore(
            restaurantId: restaurant.id,
            favoriteIds: context.favoriteIds
        )
        if favoriteScore > 0 {
            reasons.append("Nos seus favoritos")
        }

        // Context Score (0-100)
        let contextScore = calculateContextScore(
            restaurant: restaurant,
            context: context
        )
        if contextScore > 70 {
            reasons.append("Ótimo para \(context.timeContext.rawValue.lowercased())")
        }

        // WhatsApp bonus
        if restaurant.hasWhatsApp {
            reasons.append("WhatsApp disponível")
        }

        return OpportunityScore(
            distanceScore: distanceScore,
            favoriteScore: favoriteScore,
            contextScore: contextScore,
            reasons: reasons
        )
    }

    /// Rank restaurants by opportunity score
    func rankRestaurants(
        _ restaurants: [Restaurant],
        context: UserContext
    ) -> [ScoredRestaurant] {
        restaurants
            .map { restaurant in
                ScoredRestaurant(
                    restaurant: restaurant,
                    score: calculateScore(for: restaurant, context: context)
                )
            }
            .sorted { $0.score.total > $1.score.total }
    }

    /// Get top restaurants by score
    func topRestaurants(
        _ restaurants: [Restaurant],
        context: UserContext,
        limit: Int = 5
    ) -> [ScoredRestaurant] {
        Array(rankRestaurants(restaurants, context: context).prefix(limit))
    }

    // MARK: - Private Score Calculations

    private func calculateDistanceScore(_ distance: CLLocationDistance?) -> Double {
        guard let distance = distance else { return 50 } // Unknown distance = average

        if distance <= 0 { return 100 }
        if distance >= maxDistance { return 0 }

        // Linear interpolation: closer = higher score
        let normalized = 1 - (distance / maxDistance)
        return normalized * 100
    }

    private func calculateFavoriteScore(
        restaurantId: UUID,
        favoriteIds: Set<UUID>
    ) -> Double {
        favoriteIds.contains(restaurantId) ? 100 : 0
    }

    private func calculateContextScore(
        restaurant: Restaurant,
        context: UserContext
    ) -> Double {
        var score: Double = 50 // Base score

        // Category match bonus
        if context.selectedCategory != .all {
            // Can't directly match category from MapKit, give neutral score
            score += 10
        }

        // Time-appropriate category bonus
        let suggestedCategories = context.timeContext.suggestedCategories

        // Check if selected category matches time suggestions
        if suggestedCategories.contains(context.selectedCategory) {
            score += 20
        }

        // Phone available bonus (can contact easily)
        if restaurant.phoneNumber != nil {
            score += 10
        }

        // URL available bonus (can research)
        if restaurant.url != nil {
            score += 10
        }

        return min(score, 100)
    }
}

// MARK: - Restaurant Extension

extension Restaurant {
    /// Calculate opportunity score with given context
    @MainActor
    func opportunityScore(context: UserContext) -> OpportunityScore {
        ScoringService.shared.calculateScore(for: self, context: context)
    }
}


     1→import Foundation
     2→import UIKit
     3→
     4→/// Service for WhatsApp integration
     5→class WhatsAppService {
     6→
     7→    // MARK: - Singleton
     8→
     9→    static let shared = WhatsAppService()
    10→
    11→    private init() {}
    12→
    13→    // MARK: - Phone Formatting
    14→
    15→    /// Format phone number for WhatsApp (Brazilian format)
    16→    func formatWhatsAppNumber(_ phone: String) -> String? {
    17→        // Remove all non-numeric characters
    18→        var digits = phone.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()
    19→
    20→        // Handle Brazilian numbers
    21→        if digits.hasPrefix("0") {
    22→            digits = String(digits.dropFirst())
    23→        }
    24→
    25→        // Add country code if missing (Brazil = 55)
    26→        if !digits.hasPrefix("55") && digits.count >= 10 {
    27→            digits = "55" + digits
    28→        }
    29→
    30→        // Validate minimum length (country + area + number)
    31→        guard digits.count >= 12 else { return nil }
    32→
    33→        return digits
    34→    }
    35→
    36→    /// Create WhatsApp URL with optional pre-filled message
    37→    func createWhatsAppURL(phone: String, message: String? = nil) -> URL? {
    38→        guard let formattedPhone = formatWhatsAppNumber(phone) else {
    39→            return nil
    40→        }
    41→
    42→        var urlString = "https://wa.me/\(formattedPhone)"
    43→
    44→        if let message = message, !message.isEmpty {
    45→            if let encodedMessage = message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
    46→                urlString += "?text=\(encodedMessage)"
    47→            }
    48→        }
    49→
    50→        return URL(string: urlString)
    51→    }
    52→
    53→    // MARK: - WhatsApp Actions
    54→
    55→    /// Check if WhatsApp can be opened
    56→    func canOpenWhatsApp() -> Bool {
    57→        guard let url = URL(string: "whatsapp://") else { return false }
    58→        return UIApplication.shared.canOpenURL(url)
    59→    }
    60→
    61→    /// Open WhatsApp chat with restaurant
    62→    @MainActor
    63→    func openWhatsApp(phone: String, restaurantName: String? = nil) {
    64→        let message: String
    65→        if let name = restaurantName {
    66→            message = "Olá! Encontrei vocês pelo FoodFinder e gostaria de mais informações sobre o \(name)."
    67→        } else {
    68→            message = "Olá! Encontrei vocês pelo FoodFinder e gostaria de mais informações."
    69→        }
    70→
    71→        guard let url = createWhatsAppURL(phone: phone, message: message) else {
    72→            return
    73→        }
    74→
    75→        UIApplication.shared.open(url)
    76→    }
    77→
    78→    /// Open WhatsApp with custom message
    79→    @MainActor
    80→    func openWhatsApp(phone: String, message: String) {
    81→        guard let url = createWhatsAppURL(phone: phone, message: message) else {
    82→            return
    83→        }
    84→
    85→        UIApplication.shared.open(url)
    86→    }
    87→
    88→    // MARK: - Validation
    89→
    90→    /// Check if phone number appears to be valid for WhatsApp
    91→    func isValidWhatsAppNumber(_ phone: String) -> Bool {
    92→        guard let formatted = formatWhatsAppNumber(phone) else {
    93→            return false
    94→        }
    95→        // Brazilian mobile numbers: 55 + 2 digit area code + 9 digit number (starts with 9)
    96→        // Total: 13 digits for mobile
    97→        return formatted.count >= 12 && formatted.count <= 13
    98→    }
    99→
   100→    /// Check if it's likely a mobile number (more likely to have WhatsApp)
   101→    func isLikelyMobile(_ phone: String) -> Bool {
   102→        guard let formatted = formatWhatsAppNumber(phone) else {
   103→            return false
   104→        }
   105→
   106→        // Brazilian mobile numbers after area code start with 9
   107→        if formatted.count >= 13 {
   108→            let afterAreaCode = formatted.dropFirst(4) // Drop 55 + 2 digit area code
   109→            return afterAreaCode.first == "9"
   110→        }
   111→
   112→        return false
   113→    }
   114→}
   115→
   116→// MARK: - Restaurant Extension
   117→
   118→extension Restaurant {
   119→    /// WhatsApp URL for this restaurant
   120→    var whatsappURL: URL? {
   121→        guard let phone = phoneNumber else { return nil }
   122→        return WhatsAppService.shared.createWhatsAppURL(phone: phone, message: nil)
   123→    }
   124→
   125→    /// Check if restaurant likely has WhatsApp
   126→    var hasWhatsApp: Bool {
   127→        guard let phone = phoneNumber else { return false }
   128→        return WhatsAppService.shared.isValidWhatsAppNumber(phone)
   129→    }
   130→
   131→    /// Open WhatsApp chat
   132→    @MainActor
   133→    func openWhatsApp() {
   134→        guard let phone = phoneNumber else { return }
   135→        WhatsAppService.shared.openWhatsApp(phone: phone, restaurantName: name)
   136→    }
   137→}
   138→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import UIKit

/// Service for WhatsApp integration
class WhatsAppService {

    // MARK: - Singleton

    static let shared = WhatsAppService()

    private init() {}

    // MARK: - Phone Formatting

    /// Format phone number for WhatsApp (Brazilian format)
    func formatWhatsAppNumber(_ phone: String) -> String? {
        // Remove all non-numeric characters
        var digits = phone.components(separatedBy: CharacterSet.decimalDigits.inverted).joined()

        // Handle Brazilian numbers
        if digits.hasPrefix("0") {
            digits = String(digits.dropFirst())
        }

        // Add country code if missing (Brazil = 55)
        if !digits.hasPrefix("55") && digits.count >= 10 {
            digits = "55" + digits
        }

        // Validate minimum length (country + area + number)
        guard digits.count >= 12 else { return nil }

        return digits
    }

    /// Create WhatsApp URL with optional pre-filled message
    func createWhatsAppURL(phone: String, message: String? = nil) -> URL? {
        guard let formattedPhone = formatWhatsAppNumber(phone) else {
            return nil
        }

        var urlString = "https://wa.me/\(formattedPhone)"

        if let message = message, !message.isEmpty {
            if let encodedMessage = message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
                urlString += "?text=\(encodedMessage)"
            }
        }

        return URL(string: urlString)
    }

    // MARK: - WhatsApp Actions

    /// Check if WhatsApp can be opened
    func canOpenWhatsApp() -> Bool {
        guard let url = URL(string: "whatsapp://") else { return false }
        return UIApplication.shared.canOpenURL(url)
    }

    /// Open WhatsApp chat with restaurant
    @MainActor
    func openWhatsApp(phone: String, restaurantName: String? = nil) {
        let message: String
        if let name = restaurantName {
            message = "Olá! Encontrei vocês pelo FoodFinder e gostaria de mais informações sobre o \(name)."
        } else {
            message = "Olá! Encontrei vocês pelo FoodFinder e gostaria de mais informações."
        }

        guard let url = createWhatsAppURL(phone: phone, message: message) else {
            return
        }

        UIApplication.shared.open(url)
    }

    /// Open WhatsApp with custom message
    @MainActor
    func openWhatsApp(phone: String, message: String) {
        guard let url = createWhatsAppURL(phone: phone, message: message) else {
            return
        }

        UIApplication.shared.open(url)
    }

    // MARK: - Validation

    /// Check if phone number appears to be valid for WhatsApp
    func isValidWhatsAppNumber(_ phone: String) -> Bool {
        guard let formatted = formatWhatsAppNumber(phone) else {
            return false
        }
        // Brazilian mobile numbers: 55 + 2 digit area code + 9 digit number (starts with 9)
        // Total: 13 digits for mobile
        return formatted.count >= 12 && formatted.count <= 13
    }

    /// Check if it's likely a mobile number (more likely to have WhatsApp)
    func isLikelyMobile(_ phone: String) -> Bool {
        guard let formatted = formatWhatsAppNumber(phone) else {
            return false
        }

        // Brazilian mobile numbers after area code start with 9
        if formatted.count >= 13 {
            let afterAreaCode = formatted.dropFirst(4) // Drop 55 + 2 digit area code
            return afterAreaCode.first == "9"
        }

        return false
    }
}

// MARK: - Restaurant Extension

extension Restaurant {
    /// WhatsApp URL for this restaurant
    var whatsappURL: URL? {
        guard let phone = phoneNumber else { return nil }
        return WhatsAppService.shared.createWhatsAppURL(phone: phone, message: nil)
    }

    /// Check if restaurant likely has WhatsApp
    var hasWhatsApp: Bool {
        guard let phone = phoneNumber else { return false }
        return WhatsAppService.shared.isValidWhatsAppNumber(phone)
    }

    /// Open WhatsApp chat
    @MainActor
    func openWhatsApp() {
        guard let phone = phoneNumber else { return }
        WhatsAppService.shared.openWhatsApp(phone: phone, restaurantName: name)
    }
}


     1→import Foundation
     2→import SwiftUI
     3→
     4→/// Score badge levels
     5→enum ScoreBadge: String, CaseIterable {
     6→    case excellent = "Excelente"
     7→    case good = "Bom"
     8→    case average = "Regular"
     9→    case low = "Baixo"
    10→
    11→    var color: Color {
    12→        switch self {
    13→        case .excellent: return .green
    14→        case .good: return .blue
    15→        case .average: return .orange
    16→        case .low: return .gray
    17→        }
    18→    }
    19→
    20→    var icon: String {
    21→        switch self {
    22→        case .excellent: return "star.fill"
    23→        case .good: return "hand.thumbsup.fill"
    24→        case .average: return "equal.circle.fill"
    25→        case .low: return "arrow.down.circle.fill"
    26→        }
    27→    }
    28→
    29→    static func from(score: Double) -> ScoreBadge {
    30→        switch score {
    31→        case 80...100: return .excellent
    32→        case 60..<80: return .good
    33→        case 40..<60: return .average
    34→        default: return .low
    35→        }
    36→    }
    37→}
    38→
    39→/// Time context for scoring
    40→enum TimeContext: String {
    41→    case breakfast = "Café da manhã"
    42→    case lunch = "Almoço"
    43→    case afternoon = "Lanche"
    44→    case dinner = "Jantar"
    45→    case lateNight = "Noite"
    46→
    47→    var icon: String {
    48→        switch self {
    49→        case .breakfast: return "sunrise.fill"
    50→        case .lunch: return "sun.max.fill"
    51→        case .afternoon: return "cup.and.saucer.fill"
    52→        case .dinner: return "moon.stars.fill"
    53→        case .lateNight: return "moon.zzz.fill"
    54→        }
    55→    }
    56→
    57→    var suggestedCategories: [FoodCategory] {
    58→        switch self {
    59→        case .breakfast:
    60→            return [.cafe, .bakery]
    61→        case .lunch:
    62→            return [.all, .fastFood, .burger]
    63→        case .afternoon:
    64→            return [.cafe, .bakery]
    65→        case .dinner:
    66→            return [.all, .pizza, .sushi, .italian]
    67→        case .lateNight:
    68→            return [.fastFood, .burger, .pizza]
    69→        }
    70→    }
    71→
    72→    static func current() -> TimeContext {
    73→        let hour = Calendar.current.component(.hour, from: Date())
    74→
    75→        switch hour {
    76→        case 6..<10: return .breakfast
    77→        case 10..<14: return .lunch
    78→        case 14..<18: return .afternoon
    79→        case 18..<22: return .dinner
    80→        default: return .lateNight
    81→        }
    82→    }
    83→}
    84→
    85→/// Opportunity score for a restaurant
    86→struct OpportunityScore {
    87→    let total: Double // 0-100
    88→    let distanceScore: Double
    89→    let favoriteScore: Double
    90→    let contextScore: Double
    91→
    92→    var badge: ScoreBadge {
    93→        ScoreBadge.from(score: total)
    94→    }
    95→
    96→    var reasons: [String]
    97→
    98→    /// Create a basic opportunity score
    99→    init(
   100→        distanceScore: Double,
   101→        favoriteScore: Double,
   102→        contextScore: Double,
   103→        reasons: [String] = []
   104→    ) {
   105→        self.distanceScore = distanceScore
   106→        self.favoriteScore = favoriteScore
   107→        self.contextScore = contextScore
   108→
   109→        // Weighted calculation
   110→        // Distance: 40%, Favorite: 30%, Context: 30%
   111→        self.total = (distanceScore * 0.4) + (favoriteScore * 0.3) + (contextScore * 0.3)
   112→
   113→        self.reasons = reasons
   114→    }
   115→
   116→    /// Formatted score string
   117→    var formattedScore: String {
   118→        String(format: "%.0f", total)
   119→    }
   120→}
   121→
   122→/// Restaurant with calculated score
   123→struct ScoredRestaurant: Identifiable {
   124→    let restaurant: Restaurant
   125→    let score: OpportunityScore
   126→
   127→    var id: UUID { restaurant.id }
   128→}
   129→
   130→/// User context for scoring
   131→struct UserContext {
   132→    let timeContext: TimeContext
   133→    let selectedCategory: FoodCategory
   134→    let favoriteIds: Set<UUID>
   135→
   136→    init(
   137→        timeContext: TimeContext = .current(),
   138→        selectedCategory: FoodCategory = .all,
   139→        favoriteIds: Set<UUID> = []
   140→    ) {
   141→        self.timeContext = timeContext
   142→        self.selectedCategory = selectedCategory
   143→        self.favoriteIds = favoriteIds
   144→    }
   145→}
   146→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation
import SwiftUI

/// Score badge levels
enum ScoreBadge: String, CaseIterable {
    case excellent = "Excelente"
    case good = "Bom"
    case average = "Regular"
    case low = "Baixo"

    var color: Color {
        switch self {
        case .excellent: return .green
        case .good: return .blue
        case .average: return .orange
        case .low: return .gray
        }
    }

    var icon: String {
        switch self {
        case .excellent: return "star.fill"
        case .good: return "hand.thumbsup.fill"
        case .average: return "equal.circle.fill"
        case .low: return "arrow.down.circle.fill"
        }
    }

    static func from(score: Double) -> ScoreBadge {
        switch score {
        case 80...100: return .excellent
        case 60..<80: return .good
        case 40..<60: return .average
        default: return .low
        }
    }
}

/// Time context for scoring
enum TimeContext: String {
    case breakfast = "Café da manhã"
    case lunch = "Almoço"
    case afternoon = "Lanche"
    case dinner = "Jantar"
    case lateNight = "Noite"

    var icon: String {
        switch self {
        case .breakfast: return "sunrise.fill"
        case .lunch: return "sun.max.fill"
        case .afternoon: return "cup.and.saucer.fill"
        case .dinner: return "moon.stars.fill"
        case .lateNight: return "moon.zzz.fill"
        }
    }

    var suggestedCategories: [FoodCategory] {
        switch self {
        case .breakfast:
            return [.cafe, .bakery]
        case .lunch:
            return [.all, .fastFood, .burger]
        case .afternoon:
            return [.cafe, .bakery]
        case .dinner:
            return [.all, .pizza, .sushi, .italian]
        case .lateNight:
            return [.fastFood, .burger, .pizza]
        }
    }

    static func current() -> TimeContext {
        let hour = Calendar.current.component(.hour, from: Date())

        switch hour {
        case 6..<10: return .breakfast
        case 10..<14: return .lunch
        case 14..<18: return .afternoon
        case 18..<22: return .dinner
        default: return .lateNight
        }
    }
}

/// Opportunity score for a restaurant
struct OpportunityScore {
    let total: Double // 0-100
    let distanceScore: Double
    let favoriteScore: Double
    let contextScore: Double

    var badge: ScoreBadge {
        ScoreBadge.from(score: total)
    }

    var reasons: [String]

    /// Create a basic opportunity score
    init(
        distanceScore: Double,
        favoriteScore: Double,
        contextScore: Double,
        reasons: [String] = []
    ) {
        self.distanceScore = distanceScore
        self.favoriteScore = favoriteScore
        self.contextScore = contextScore

        // Weighted calculation
        // Distance: 40%, Favorite: 30%, Context: 30%
        self.total = (distanceScore * 0.4) + (favoriteScore * 0.3) + (contextScore * 0.3)

        self.reasons = reasons
    }

    /// Formatted score string
    var formattedScore: String {
        String(format: "%.0f", total)
    }
}

/// Restaurant with calculated score
struct ScoredRestaurant: Identifiable {
    let restaurant: Restaurant
    let score: OpportunityScore

    var id: UUID { restaurant.id }
}

/// User context for scoring
struct UserContext {
    let timeContext: TimeContext
    let selectedCategory: FoodCategory
    let favoriteIds: Set<UUID>

    init(
        timeContext: TimeContext = .current(),
        selectedCategory: FoodCategory = .all,
        favoriteIds: Set<UUID> = []
    ) {
        self.timeContext = timeContext
        self.selectedCategory = selectedCategory
        self.favoriteIds = favoriteIds
    }
}


Excellent! Now let me compile my comprehensive findings into a clear report:

## FoodFinder iOS App Codebase Exploration - Summary Report

I've thoroughly explored the FoodFinder-iOS app codebase and found a well-structured project with AI integration, restaurant discovery, and multiple delivery integration capabilities. Here are my detailed findings:

### 1. Current Restaurant Model Structure

**File:** `/Users/neog/Apps/iOS/FoodFinder-iOS/FoodFinder-iOS/Models/Restaurant.swift`

**Available Data:**
- `id` - UUID identifier
- `name` - Restaurant name
- `category` - MapKit POI category (e.g., "restaurant", "pizza")
- `phoneNumber` - Phone contact (with cleaning for tel:// deep links)
- `url` - Website URL
- `coordinate` - CLLocationCoordinate2D for mapping
- `address` - Formatted street address
- `mapItem` - Original MKMapItem from MapKit
- `distanceFromUser` - Calculated distance
- `formattedDistance` - Human-readable distance string ("500 m" or "1.2 km")

**Extended Data Available:**
- WhatsApp capability detection (via WhatsAppService extension)
- Opportunity scoring (via ScoringService extension)
- Favorite status tracking

### 2. Claude Service Implementation

**File:** `/Users/neog/Apps/iOS/FoodFinder-iOS/FoodFinder-iOS/Services/ClaudeService.swift`

**How Context is Built:**
```
ClaudeService.buildContext() → Creates structured context with:
├── Category selecionada: [category name]
├── RESTAURANTES PRÓXIMOS (up to 15):
│   └── [Index]. [Name] - [Distance] - [Address] - [Phone]
└── FAVORITOS DO USUÁRIO (up to 10):
    └── [Name] ([Address])
```

**Key Features:**
- Model: `claude-sonnet-4-20250514`
- System prompt in Portuguese (Brazilian)
- Message limit: 1024 tokens
- Accepts: question, restaurants list, favorites, category
- Returns: Plain text response with restaurant recommendations

**Note:** API key is hardcoded in the file (potential security issue for production)

### 3. RestaurantService Implementation

**File:** `/Users/neog/Apps/iOS/FoodFinder-iOS/FoodFinder-iOS/Services/RestaurantService.swift`

**Search Mechanism:**
- Uses `MKLocalSearch` from MapKit (native iOS)
- Search radius: 2km by default
- Searches by category rawValue (e.g., "restaurant", "pizza", "sushi")
- Returns `[Restaurant]` sorted by distance

**FoodCategory Enum:**
```
all, pizza, burger, sushi, chinese, mexican, italian, cafe, fastFood, bakery
```

**Data Flow:**
1. User location + category → `searchNearby()`
2. MKLocalSearch request with region
3. Results mapped to Restaurant objects
4. Sorted by distance, published via @Published property

**Limitations:**
- Only MapKit data available (no restaurant hours, ratings, menus, or delivery availability)
- Category detection relies on MapKit's POI classification

### 4. API Integration Patterns from Other iOS Apps

I found several well-documented patterns used across your iOS ecosystem:

#### **Pattern 1: Generic API Service with REST Methods**
From `/Users/neog/Apps/iOS/NEOGBusiness-iOS/Services/APIService.swift`:
- Singleton pattern with URLSession configuration
- Generic `request<T>()` method supporting GET, POST, PUT, DELETE
- Automatic snake_case/camelCase conversion
- Cookie handling for session persistence
- Error mapping with localized messages

#### **Pattern 2: Multiple AI Provider Integration**
From `/Users/neog/Apps/iOS/BusinessPlan-iOS/Services/AIService.swift`:
- Supports 3 providers: LM Studio (local), Grok, Claude
- Provider enum with baseURL, model, apiKey, color properties
- Separate methods per provider (callLMStudioAPI, callGrokAPI, callClaudeAPI)
- Unified interface with error handling
- Context enrichment from secondary services (Obsidian)

#### **Pattern 3: Third-Party API Integration (Obsidian)**
From `/Users/neog/Apps/iOS/BusinessPlan-iOS/Services/ObsidianService.swift`:
- Custom URLSessionDelegate for self-signed certificates
- Caching layer for API responses (5-minute TTL)
- Comprehensive error mapping
- Bearer token authentication
- Base URL + path building pattern

#### **Pattern 4: WhatsApp Integration**
From `/Users/neog/Apps/iOS/FoodFinder-iOS/FoodFinder-iOS/Services/WhatsAppService.swift`:
- Phone number formatting for Brazilian numbers
- Deep linking via `https://wa.me/[phone]` URL scheme
- Pre-filled message support
- Validation for mobile numbers vs. landlines
- Extension methods on Restaurant model

### 5. Scoring & Opportunity Analysis

**File:** `/Users/neog/Apps/iOS/FoodFinder-iOS/FoodFinder-iOS/Services/ScoringService.swift`

The app already calculates restaurant opportunity scores with:
- **Distance Score** (0-100): Closer = higher
- **Favorite Score** (0-100): Is it in user's favorites?
- **Context Score** (0-100): Matches time of day + category
- **WhatsApp bonus**: Points for WhatsApp availability

**Time Context Integration:**
- Breakfast (6-10am): Suggests cafes, bakeries
- Lunch (10-2pm): All, fast food, burgers
- Afternoon (2-6pm): Cafes, bakeries
- Dinner (6-10pm): All, pizza, sushi, Italian
- Late Night (10pm-6am): Fast food, burgers, pizza

### 6. Current FoodFinder Architecture

```
FoodFinder-iOS/
├── Services/
│   ├── ClaudeService.swift          ← AI recommendations
│   ├── RestaurantService.swift      ← MapKit searches
│   ├── LocationService.swift        ← CoreLocation
│   ├── FavoritesService.swift       ← UserDefaults persistence
│   ├── ScoringService.swift         ← Opportunity scoring
│   ├── WhatsAppService.swift        ← WhatsApp deep links
│   ├── AuthenticationService.swift  ← Apple Sign In
│   └── KeychainService.swift        ← Secure storage
│
├── Models/
│   ├── Restaurant.swift             ← MapKit wrapper
│   ├── ChatMessage.swift            ← Chat UI model
│   ├── OpportunityScore.swift       ← Scoring model
│   └── User.swift                   ← Auth user
│
└── Views/
    ├── MainView.swift               ← Map + List UI
    ├── AIChatView.swift             ← Claude chat interface
    ├── RestaurantDetailView.swift   ← Details + actions
    └── FavoritesView.swift          ← Favorites list
```

### 7. Recommendations for iFood/Uber Eats Integration

Based on the patterns found, here's how you could integrate delivery apps:

**Option A: Web Scraping Service** (similar to Obsidian pattern)
- Create `FoodDeliveryService.swift` with REST client
- Support multiple APIs: iFood, Uber Eats, Rappi
- Cache menu items and delivery info
- Parse HTML/JSON responses

**Option B: Direct API Integration** (similar to BusinessPlan AIService)
- Each delivery app enum case with baseURL, API key
- Provider-specific parsing logic
- Unified interface: `getRestaurantMenu(id:)`, `checkDeliveryAvailability()`

**Option C: Web Scraping + Selenium** (for complex sites)
- Python backend via webhook (like your other projects)
- Return parsed menu + pricing
- Call from Swift via URLSession

**Current Status:** No existing iFood/Uber Eats integration patterns found in the workspace.

---

All file paths are absolute and ready for implementation planning. The codebase demonstrates strong patterns for API integration, error handling, and service architecture that can be directly applied to delivery app integration.