import { MaterialCommunityIcons } from "@expo/vector-icons";
// Every control on this screen lives inside the RideLayout bottom sheet, and
// on Android a react-native touchable in there loses its first press to the
// sheet's gesture handler — which is why "Find now" had to be tapped twice to
// send a request. The sheet's own touchables are the fix the library ships for
// this; on iOS they are react-native's, unchanged.
import { TouchableOpacity } from "@gorhom/bottom-sheet";
import { router } from "expo-router";
import { useEffect, useState } from "react";
import { Alert, Text, View } from "react-native";

import { CustomButton } from "@/components/custom-button";
import { GoogleTextInput } from "@/components/google-text-input";
import { RideLayout } from "@/components/ride-layout";
import { icons } from "@/constants";
import { SERVICES, type ServiceId } from "@/constants/services";
import { ApiError } from "@/lib/fetch";
import { useT } from "@/lib/i18n";
import { calculateTripFare } from "@/lib/map";
import { formatLBP } from "@/lib/pricing";
import { createRideRequest } from "@/lib/request-ride";
import { useServiceAvailability } from "@/lib/use-service-availability";
import { formatTime } from "@/lib/utils";
import { useLocationStore, useServiceStore } from "@/store";

/**
 * "Set it on the map" for one of the two points.
 *
 * An autocomplete result lands on whatever the geocoder calls the centre of a
 * place, which is regularly the wrong side of a building or the wrong end of a
 * long street — and a driver sent to the wrong side of a divided road can't
 * simply turn around. This is the escape hatch: the rider drags the map to the
 * exact doorway.
 */
const AdjustOnMap = ({ mode }: { mode: "origin" | "destination" }) => {
  const t = useT();

  return (
    <TouchableOpacity
      onPress={() =>
        router.push({ pathname: "/(root)/adjust-pin", params: { mode } })
      }
      className="flex-row items-center gap-x-2 mt-2 self-start px-1 py-1.5"
    >
      <MaterialCommunityIcons
        name="map-marker-radius"
        size={16}
        color="#0286ff"
      />
      <Text className="text-sm font-JakartaBold text-primary-500">
        {t("findRide.adjustOnMap")}
      </Text>
    </TouchableOpacity>
  );
};

/**
 * Which service the request goes out on, with live availability.
 *
 * It lives on this screen because this is now the last screen before drivers
 * are contacted — the request is broadcast on tap, so the choice of who to
 * broadcast it to has to be made here, next to the button that sends it.
 */
const ServiceRow = ({
  service,
  counts,
  onSelect,
}: {
  service: ServiceId;
  counts: Record<ServiceId, number>;
  onSelect: (id: ServiceId) => void;
}) => {
  const t = useT();

  return (
    <View className="flex-row gap-2">
      {SERVICES.map((item) => {
        const active = item.id === service;
        const available = counts[item.id] ?? 0;

        return (
          <TouchableOpacity
            key={item.id}
            onPress={() => onSelect(item.id)}
            activeOpacity={0.8}
            accessibilityRole="button"
            accessibilityState={{ selected: active }}
            className={`flex-1 items-center rounded-2xl border py-2.5 ${
              active
                ? "border-primary-500 bg-primary-500/10"
                : "border-neutral-100 bg-neutral-100 dark:border-neutral-800 dark:bg-neutral-800"
            }`}
          >
            <MaterialCommunityIcons
              name={item.icon}
              size={20}
              color={active ? "#0286ff" : "#858585"}
            />
            <Text
              className={`text-[11px] mt-1 font-JakartaMedium ${
                active
                  ? "text-primary-500"
                  : "text-general-200 dark:text-neutral-400"
              }`}
            >
              {t(item.labelKey)}
            </Text>
            {/* The count is the honest version of an empty map: it says
                whether asking this service is worth doing before the rider
                sends a request nobody will answer. */}
            <Text
              className={`text-[10px] ${
                available > 0
                  ? "text-emerald-600 dark:text-emerald-400"
                  : "text-general-200 dark:text-neutral-500"
              }`}
            >
              {available > 0 ? t("findRide.nAvailable", { n: available }) : "—"}
            </Text>
          </TouchableOpacity>
        );
      })}
    </View>
  );
};

const FindRide = () => {
  const t = useT();
  const {
    userAddress,
    destinationAddress,
    userLatitude,
    userLongitude,
    destinationLatitude,
    destinationLongitude,
    setDestinationLocation,
    setUserLocation,
  } = useLocationStore();
  const { service, setService } = useServiceStore();

  const [estimate, setEstimate] = useState<{
    fare: string;
    durationSeconds: number;
  } | null>(null);
  const [estimating, setEstimating] = useState(false);
  const [sending, setSending] = useState(false);

  const hasRoute =
    !!userLatitude &&
    !!userLongitude &&
    !!destinationLatitude &&
    !!destinationLongitude;

  const { counts } = useServiceAvailability(userLatitude, userLongitude);

  // The fare is quoted before the request goes out, not after: it is what the
  // drivers deciding whether to take the job are shown, so it has to exist by
  // the time the request does. Recomputed when the route or service changes.
  useEffect(() => {
    if (!hasRoute) {
      setEstimate(null);
      return;
    }

    let cancelled = false;
    setEstimating(true);

    void calculateTripFare({
      userLatitude,
      userLongitude,
      destinationLatitude,
      destinationLongitude,
      service,
    })
      .then((trip) => {
        if (cancelled) return;
        setEstimate(
          trip
            ? { fare: trip.fare, durationSeconds: trip.durationSeconds }
            : null,
        );
      })
      .finally(() => {
        if (!cancelled) setEstimating(false);
      });

    return () => {
      cancelled = true;
    };
  }, [
    hasRoute,
    userLatitude,
    userLongitude,
    destinationLatitude,
    destinationLongitude,
    service,
  ]);

  const findNow = async () => {
    if (!hasRoute || !estimate) return;

    setSending(true);
    try {
      const ride = await createRideRequest({
        service,
        origin: {
          address: userAddress ?? "",
          latitude: userLatitude!,
          longitude: userLongitude!,
        },
        destination: {
          address: destinationAddress ?? "",
          latitude: destinationLatitude!,
          longitude: destinationLongitude!,
        },
        rideTimeSeconds: estimate.durationSeconds,
        fareCents: Math.round(parseFloat(estimate.fare) * 100),
      });

      router.replace(`/(root)/book-ride?id=${ride.ride_id}`);
    } catch (err) {
      console.log("[FIND_RIDE]: ", err);

      // The rider already has a ride in flight. Booking a second one isn't
      // what they want — they want the one they lost track of, so take them
      // to it instead of showing an error they can't act on.
      if (
        err instanceof ApiError &&
        err.status === 409 &&
        err.body?.code === "RIDE_IN_PROGRESS"
      ) {
        const inProgressId = String(err.body.ride_id);
        Alert.alert(
          t("confirmRide.alertInProgressTitle"),
          t("confirmRide.alertInProgressBody"),
          [
            { text: t("common.cancel"), style: "cancel" },
            {
              text: t("confirmRide.viewRide"),
              onPress: () =>
                router.replace(`/(root)/book-ride?id=${inProgressId}`),
            },
          ],
        );
        return;
      }

      Alert.alert(
        t("confirmRide.alertErrorTitle"),
        err instanceof ApiError
          ? err.message
          : t("confirmRide.alertErrorFallback"),
      );
    } finally {
      setSending(false);
    }
  };

  return (
    <RideLayout title={t("findRide.title")} snapPoints={["85%"]}>
      <View className="my-3">
        <Text className="text-lg font-JakartaSemiBold mb-3 text-black dark:text-white">
          {t("findRide.from")}
        </Text>

        <GoogleTextInput
          icon={icons.target}
          initialLocation={userAddress ?? ""}
          containerStyles="bg-neutral-100 dark:bg-neutral-800"
          handlePress={setUserLocation}
        />

        <AdjustOnMap mode="origin" />
      </View>

      <View className="my-3">
        <Text className="text-lg font-JakartaSemiBold mb-3 text-black dark:text-white">
          {t("findRide.to")}
        </Text>

        <GoogleTextInput
          icon={icons.map}
          initialLocation={destinationAddress ?? ""}
          containerStyles="bg-neutral-100 dark:bg-neutral-800"
          handlePress={setDestinationLocation}
        />

        <AdjustOnMap mode="destination" />
      </View>

      <Text className="text-sm font-JakartaSemiBold mb-2 mt-1 text-black dark:text-white">
        {t("findRide.service")}
      </Text>
      <ServiceRow service={service} counts={counts} onSelect={setService} />

      {/* The quote, shown before the request goes out rather than on a screen
          after it. This is the number the rider agrees to and the number every
          driver who sees the request is offered, so it belongs next to the
          button that sends it. */}
      <View className="flex-row items-center justify-between rounded-2xl bg-general-500 dark:bg-neutral-950 px-4 py-3 mt-4">
        <View>
          <Text className="text-xs font-JakartaMedium text-general-200 dark:text-neutral-400">
            {t("findRide.estimatedFare")}
          </Text>
          <Text className="text-[11px] text-general-200 dark:text-neutral-400 mt-0.5">
            {estimate
              ? t("confirmRide.tripTime", {
                  time: formatTime(estimate.durationSeconds / 60),
                })
              : t("findRide.setBothPoints")}
          </Text>
        </View>
        <View className="items-end">
          <Text className="text-2xl font-JakartaExtraBold text-black dark:text-white">
            {estimating ? "…" : estimate ? `$${estimate.fare}` : "—"}
          </Text>
          {estimate ? (
            <Text className="text-[11px] text-general-200 dark:text-neutral-400">
              {t("confirmRide.lbpEstimate", {
                lbp: formatLBP(parseFloat(estimate.fare)),
              })}
            </Text>
          ) : null}
        </View>
      </View>

      <Text className="text-[11px] text-center text-general-200 dark:text-neutral-400 mt-3">
        {t("findRide.payLaterHint")}
      </Text>

      <CustomButton
        Touchable={TouchableOpacity}
        title={sending ? t("findRide.sending") : t("findRide.findNow")}
        onPress={() => void findNow()}
        disabled={!hasRoute || !estimate || estimating || sending}
        className={`mt-3 ${!hasRoute || !estimate || estimating || sending ? "opacity-50" : ""}`}
      />
    </RideLayout>
  );
};

export default FindRide;
