import { MaterialCommunityIcons } from "@expo/vector-icons";
import type * as ImagePicker from "expo-image-picker";
import { useState } from "react";
import {
  ActivityIndicator,
  Alert,
  Image,
  Text,
  TouchableOpacity,
  View,
} from "react-native";

import { alertPermissionDenied } from "@/lib/capture-permission";
import { ApiError, fetchAPI } from "@/lib/fetch";
import { loadImagePicker } from "@/lib/image-picker";
import { useT } from "@/lib/i18n";
import { useTheme } from "@/lib/theme";

/** The three documents a Lebanese driver is vetted against. */
export type DocumentType = "license" | "id" | "vehicle_reg";

/**
 * What a scan can fill in. Every field is optional and independent: a licence
 * whose number reads cleanly but whose expiry is smudged yields just the
 * number. Mirrors ExtractedFields on the server — deliberately redeclared here
 * so the client bundle doesn't pull in lib/document-ocr.ts, which is Node-only.
 */
export type ScannedFields = {
  license_number?: string;
  license_expiry?: string;
  national_id?: string;
  plate_number?: string;
  car_model?: string;
};

type ScanResponse = {
  data: {
    doc_type: DocumentType;
    document: string;
    fields: ScannedFields;
    code?: string;
  };
};

/**
 * Photographs one document, sends it for OCR, and reports back both the stored
 * scan's name (which goes with the profile submission) and whatever fields
 * were read off it.
 *
 * The component never writes to the form itself — it hands the values up, and
 * the form decides what to do with them. That separation is what lets a driver
 * correct a misread field and not have the next scan silently stamp over it.
 * A failed read is not an error state here: the scan is still stored for the
 * reviewer, and the driver types the details in by hand as before.
 */
export const DocumentScanner = ({
  docType,
  label,
  hint,
  optional = false,
  onFile = false,
  onScanned,
}: {
  docType: DocumentType;
  label: string;
  hint: string;
  optional?: boolean;
  /** A scan of this document is already stored — resubmitting may not need a new one. */
  onFile?: boolean;
  onScanned: (document: string, fields: ScannedFields) => void;
}) => {
  const t = useT();
  const { isDark } = useTheme();

  const [preview, setPreview] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);
  const [status, setStatus] = useState<string | null>(null);
  const [failed, setFailed] = useState(false);

  const upload = async (asset: ImagePicker.ImagePickerAsset) => {
    if (!asset.base64) {
      Alert.alert(t("driver.scan.errorTitle"), t("driver.scan.errorBody"));
      return;
    }

    setBusy(true);
    setStatus(null);
    setFailed(false);

    try {
      const { data } = (await fetchAPI("/(api)/driver/scan", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ doc_type: docType, image_base64: asset.base64 }),
      })) as ScanResponse;

      setPreview(asset.uri);
      onScanned(data.document, data.fields);

      const filled = Object.values(data.fields).filter(Boolean).length;

      // Three outcomes worth telling apart: OCR read something, OCR ran and
      // found nothing usable, or OCR never ran. All three keep the scan; only
      // the wording changes, because in every case the driver's next move is
      // to check the fields below.
      setStatus(
        filled > 0
          ? t("driver.scan.filled", undefined, filled)
          : data.code === "OCR_UNAVAILABLE"
            ? t("driver.scan.savedUnreadable")
            : t("driver.scan.savedNoFields"),
      );
    } catch (err) {
      console.log("[DOCUMENT_SCAN]: ", err);

      const code =
        err instanceof ApiError
          ? (err.body?.code as string | undefined)
          : undefined;

      Alert.alert(
        t("driver.scan.errorTitle"),
        code === "IMAGE_TOO_LARGE"
          ? t("driver.scan.errorTooLarge")
          : code === "SCAN_RATE_LIMIT"
            ? t("driver.scan.errorRateLimit")
            : code === "UNSUPPORTED_IMAGE"
              ? t("driver.scan.errorUnsupported")
              : t("driver.scan.errorBody"),
      );
      setFailed(true);
    } finally {
      setBusy(false);
    }
  };

  const capture = async (source: "camera" | "library") => {
    if (busy) return;

    // Loaded on demand: on a binary built before expo-image-picker was added
    // the native module is missing, and importing it at the top of this file
    // would take the whole app down instead of just this button.
    const picker = loadImagePicker();
    if (!picker) {
      Alert.alert(t("driver.scan.errorTitle"), t("driver.captureUnavailable"));
      return;
    }

    // Ask only for the permission the tapped button actually needs — a driver
    // who refuses the camera can still pick an existing photo of their papers.
    let permission: ImagePicker.PermissionResponse;

    try {
      permission =
        source === "camera"
          ? await picker.requestCameraPermissionsAsync()
          : await picker.requestMediaLibraryPermissionsAsync();
    } catch (error) {
      console.log("[DOCUMENT_SCAN_PERMISSION]: ", error);
      Alert.alert(t("driver.scan.errorTitle"), t("driver.captureUnavailable"));
      return;
    }

    if (!permission.granted) {
      alertPermissionDenied(permission, {
        title: t("driver.scan.permissionTitle"),
        message:
          source === "camera"
            ? t("driver.scan.permissionCamera")
            : t("driver.scan.permissionLibrary"),
        blocked:
          source === "camera"
            ? t("driver.scan.permissionCameraBlocked")
            : t("driver.scan.permissionLibraryBlocked"),
        openSettings: t("common.openSettings"),
        cancel: t("common.cancel"),
      });
      return;
    }

    // `quality: 0.6` keeps a phone photo comfortably under the upload cap
    // while staying sharp enough to read small print; no cropping step,
    // because OCR wants the whole card and an edited crop routinely loses the
    // line the expiry date sits on.
    const options: ImagePicker.ImagePickerOptions = {
      mediaTypes: picker.MediaTypeOptions.Images,
      quality: 0.6,
      base64: true,
      exif: false,
    };

    let result: ImagePicker.ImagePickerResult;

    try {
      result =
        source === "camera"
          ? await picker.launchCameraAsync(options)
          : await picker.launchImageLibraryAsync(options);
    } catch (error) {
      console.log("[DOCUMENT_SCAN_CAPTURE]: ", error);
      Alert.alert(t("driver.scan.errorTitle"), t("driver.captureUnavailable"));
      return;
    }

    if (result.canceled || !result.assets[0]) return;

    await upload(result.assets[0]);
  };

  const scanned = preview !== null;

  return (
    <View className="bg-neutral-100 dark:bg-neutral-900 rounded-2xl p-4 mb-4">
      <View className="flex-row items-start justify-between mb-1">
        <Text className="text-sm font-JakartaBold text-black dark:text-white flex-1 pr-2">
          {label}
        </Text>
        {optional ? (
          <Text className="text-[11px] font-JakartaSemiBold text-general-200 dark:text-neutral-500 uppercase">
            {t("driver.scan.optional")}
          </Text>
        ) : null}
      </View>

      <Text className="text-xs font-Jakarta text-general-200 dark:text-neutral-400 mb-3">
        {hint}
      </Text>

      <View className="flex-row items-center">
        {scanned ? (
          <Image
            source={{ uri: preview }}
            className="w-16 h-16 rounded-xl mr-3"
            resizeMode="cover"
            alt={label}
          />
        ) : null}

        <View className="flex-1 flex-row gap-2">
          <TouchableOpacity
            onPress={() => void capture("camera")}
            disabled={busy}
            className="flex-1 flex-row items-center justify-center rounded-full bg-primary-500 py-3 px-2"
          >
            {busy ? (
              <ActivityIndicator size="small" color="#ffffff" />
            ) : (
              <>
                <MaterialCommunityIcons
                  name="camera-outline"
                  size={16}
                  color="#ffffff"
                />
                <Text className="text-white font-JakartaBold text-xs ml-1.5">
                  {scanned ? t("driver.scan.retake") : t("driver.scan.take")}
                </Text>
              </>
            )}
          </TouchableOpacity>

          <TouchableOpacity
            onPress={() => void capture("library")}
            disabled={busy}
            className="flex-1 flex-row items-center justify-center rounded-full border border-neutral-300 dark:border-neutral-700 py-3 px-2"
          >
            <MaterialCommunityIcons
              name="image-outline"
              size={16}
              color={isDark ? "#e5e5e5" : "#333333"}
            />
            <Text className="text-black dark:text-white font-JakartaBold text-xs ml-1.5">
              {t("driver.scan.choose")}
            </Text>
          </TouchableOpacity>
        </View>
      </View>

      {busy ? (
        <Text className="text-xs font-Jakarta text-general-200 dark:text-neutral-400 mt-3">
          {t("driver.scan.reading")}
        </Text>
      ) : status ? (
        <View className="flex-row items-center mt-3">
          <MaterialCommunityIcons
            name="check-circle-outline"
            size={14}
            color="#10b981"
          />
          <Text className="text-xs font-JakartaSemiBold text-emerald-600 dark:text-emerald-400 ml-1.5 flex-1">
            {status}
          </Text>
        </View>
      ) : failed ? (
        <View className="flex-row items-center mt-3">
          <MaterialCommunityIcons
            name="alert-outline"
            size={14}
            color="#f43f5e"
          />
          <Text className="text-xs font-JakartaSemiBold text-rose-500 ml-1.5 flex-1">
            {t("driver.scan.errorRetry")}
          </Text>
        </View>
      ) : onFile ? (
        // Resubmitting after a rejection: the reviewer already has a scan, so
        // say so rather than making the driver wonder whether it was lost.
        <View className="flex-row items-center mt-3">
          <MaterialCommunityIcons
            name="paperclip"
            size={14}
            color={isDark ? "#9ca3af" : "#858585"}
          />
          <Text className="text-xs font-Jakarta text-general-200 dark:text-neutral-400 ml-1.5 flex-1">
            {t("driver.scan.alreadyOnFile")}
          </Text>
        </View>
      ) : null}
    </View>
  );
};
